1 /************************** BEGIN JuceGUI.h **************************/
2 /************************************************************************
3  FAUST Architecture File
4  Copyright (C) 2003-2017 GRAME, Centre National de Creation Musicale
5  ---------------------------------------------------------------------
6  This Architecture section is free software; you can redistribute it
7  and/or modify it under the terms of the GNU General Public License
8  as published by the Free Software Foundation; either version 3 of
9  the License, or (at your option) any later version.
10 
11  This program is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with this program; If not, see <http://www.gnu.org/licenses/>.
18 
19  EXCEPTION : As a special exception, you may create a larger work
20  that contains this FAUST architecture section and distribute
21  that work under terms of your choice, so long as this FAUST
22  architecture section is not modified.
23  ************************************************************************/
24 
25 #ifndef JUCE_GUI_H
26 #define JUCE_GUI_H
27 
28 #ifndef FAUSTFLOAT
29 #define FAUSTFLOAT float
30 #endif
31 
32 #include <stack>
33 
34 #include "../JuceLibraryCode/JuceHeader.h"
35 
36 #include "faust/gui/GUI.h"
37 #include "faust/gui/MetaDataUI.h"
38 #include "faust/gui/ValueConverter.h"
39 
40 // Definition of the standard size of the different elements
41 
42 #define kKnobWidth 100
43 #define kKnobHeight 100
44 
45 #define kVSliderWidth 80
46 #define kVSliderHeight 250
47 
48 #define kHSliderWidth 350
49 #define kHSliderHeight 50
50 
51 #define kButtonWidth 100
52 #define kButtonHeight 50
53 
54 #define kCheckButtonWidth 60
55 #define kCheckButtonHeight 40
56 
57 #define kMenuWidth 100
58 #define kMenuHeight 50
59 
60 #define kRadioButtonWidth 100
61 #define kRadioButtonHeight 55
62 
63 #define kNumEntryWidth 100
64 #define kNumEntryHeight 50
65 
66 #define kNumDisplayWidth 75
67 #define kNumDisplayHeight 50
68 
69 #define kVBargraphWidth 60
70 #define kVBargraphHeight 250
71 
72 #define kHBargraphWidth 350
73 #define kHBargraphHeight 50
74 
75 #define kLedWidth 25
76 #define kLedHeight 25
77 
78 #define kNameHeight 14
79 
80 #define kMargin 4
81 
82 /**
83  * \brief       Custom LookAndFeel class.
84  * \details     Define the appearance of all the JUCE widgets.
85  */
86 
87 struct CustomLookAndFeel : public juce::LookAndFeel_V3
88 {
drawRoundThumbCustomLookAndFeel89     void drawRoundThumb (juce::Graphics& g, const float x, const float y,
90                          const float diameter, const juce::Colour& colour, float outlineThickness)
91     {
92         const juce::Rectangle<float> a (x, y, diameter, diameter);
93         const float halfThickness = outlineThickness * 0.5f;
94 
95         juce::Path p;
96         p.addEllipse (x + halfThickness, y + halfThickness, diameter - outlineThickness, diameter - outlineThickness);
97 
98         const juce::DropShadow ds (juce::Colours::black, 1, juce::Point<int> (0, 0));
99         ds.drawForPath (g, p);
100 
101         g.setColour (colour);
102         g.fillPath (p);
103 
104         g.setColour (colour.brighter());
105         g.strokePath (p, juce::PathStrokeType (outlineThickness));
106     }
107 
drawButtonBackgroundCustomLookAndFeel108     void drawButtonBackground (juce::Graphics& g, juce::Button& button, const juce::Colour& backgroundColour,
109                                bool isMouseOverButton, bool isButtonDown) override
110     {
111         juce::Colour baseColour (backgroundColour.withMultipliedSaturation (button.hasKeyboardFocus (true) ? 1.3f : 0.9f)
112                            .withMultipliedAlpha (button.isEnabled() ? 0.9f : 0.5f));
113 
114         if (isButtonDown || isMouseOverButton)
115             baseColour = baseColour.contrasting (isButtonDown ? 0.2f : 0.1f);
116 
117         const bool flatOnLeft   = button.isConnectedOnLeft();
118         const bool flatOnRight  = button.isConnectedOnRight();
119         const bool flatOnTop    = button.isConnectedOnTop();
120         const bool flatOnBottom = button.isConnectedOnBottom();
121 
122         const float width  = button.getWidth() - 1.0f;
123         const float height = button.getHeight() - 1.0f;
124 
125         if (width > 0 && height > 0)
126         {
127             const float cornerSize = juce::jmin(15.0f, juce::jmin(width, height) * 0.45f);
128             const float lineThickness = cornerSize * 0.1f;
129             const float halfThickness = lineThickness * 0.5f;
130 
131             juce::Path outline;
132             outline.addRoundedRectangle (0.5f + halfThickness, 0.5f + halfThickness, width - lineThickness, height - lineThickness,
133                                          cornerSize, cornerSize,
134                                          ! (flatOnLeft  || flatOnTop),
135                                          ! (flatOnRight || flatOnTop),
136                                          ! (flatOnLeft  || flatOnBottom),
137                                          ! (flatOnRight || flatOnBottom));
138 
139             const juce::Colour outlineColour (button.findColour (button.getToggleState() ? juce::TextButton::textColourOnId
140                                         : juce::TextButton::textColourOffId));
141 
142             g.setColour (baseColour);
143             g.fillPath (outline);
144 
145             if (! button.getToggleState()) {
146                 g.setColour (outlineColour);
147                 g.strokePath (outline, juce::PathStrokeType (lineThickness));
148             }
149         }
150     }
151 
drawTickBoxCustomLookAndFeel152     void drawTickBox (juce::Graphics& g, juce::Component& component,
153                       float x, float y, float w, float h,
154                       bool ticked,
155                       bool isEnabled,
156                       bool isMouseOverButton,
157                       bool isButtonDown) override
158     {
159         const float boxSize = w * 0.7f;
160 
161         bool isDownOrDragging = component.isEnabled() && (component.isMouseOverOrDragging() || component.isMouseButtonDown());
162         const juce::Colour colour (component.findColour (juce::TextButton::buttonColourId).withMultipliedSaturation ((component.hasKeyboardFocus (false) || isDownOrDragging) ? 1.3f : 0.9f)
163                              .withMultipliedAlpha (component.isEnabled() ? 1.0f : 0.7f));
164 
165         drawRoundThumb (g, x, y + (h - boxSize) * 0.5f, boxSize, colour,
166                         isEnabled ? ((isButtonDown || isMouseOverButton) ? 1.1f : 0.5f) : 0.3f);
167 
168         if (ticked) {
169             const juce::Path tick (juce::LookAndFeel_V2::getTickShape (6.0f));
170             g.setColour (isEnabled ? findColour (juce::TextButton::buttonOnColourId) : juce::Colours::grey);
171 
172             const float scale = 9.0f;
173             const juce::AffineTransform trans (juce::AffineTransform::scale (w / scale, h / scale)
174                                          .translated (x - 2.5f, y + 1.0f));
175             g.fillPath (tick, trans);
176         }
177     }
178 
drawLinearSliderThumbCustomLookAndFeel179     void drawLinearSliderThumb (juce::Graphics& g, int x, int y, int width, int height,
180                                 float sliderPos, float minSliderPos, float maxSliderPos,
181                                 const juce::Slider::SliderStyle style, juce::Slider& slider) override
182     {
183         const float sliderRadius = (float)(getSliderThumbRadius (slider) - 2);
184 
185         bool isDownOrDragging = slider.isEnabled() && (slider.isMouseOverOrDragging() || slider.isMouseButtonDown());
186         juce::Colour knobColour (slider.findColour (juce::Slider::thumbColourId).withMultipliedSaturation ((slider.hasKeyboardFocus (false) || isDownOrDragging) ? 1.3f : 0.9f)
187                            .withMultipliedAlpha (slider.isEnabled() ? 1.0f : 0.7f));
188 
189         if (style == juce::Slider::LinearHorizontal || style == juce::Slider::LinearVertical) {
190             float kx, ky;
191 
192             if (style == juce::Slider::LinearVertical) {
193                 kx = x + width * 0.5f;
194                 ky = sliderPos;
195             } else {
196                 kx = sliderPos;
197                 ky = y + height * 0.5f;
198             }
199 
200             const float outlineThickness = slider.isEnabled() ? 0.8f : 0.3f;
201 
202             drawRoundThumb (g,
203                             kx - sliderRadius,
204                             ky - sliderRadius,
205                             sliderRadius * 2.0f,
206                             knobColour, outlineThickness);
207         } else {
208             // Just call the base class for the demo
209             juce::LookAndFeel_V2::drawLinearSliderThumb (g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, style, slider);
210         }
211     }
212 
drawLinearSliderCustomLookAndFeel213     void drawLinearSlider (juce::Graphics& g, int x, int y, int width, int height,
214                            float sliderPos, float minSliderPos, float maxSliderPos,
215                            const juce::Slider::SliderStyle style, juce::Slider& slider) override
216     {
217         g.fillAll (slider.findColour (juce::Slider::backgroundColourId));
218 
219         if (style == juce::Slider::LinearBar || style == juce::Slider::LinearBarVertical) {
220             const float fx = (float)x, fy = (float)y, fw = (float)width, fh = (float)height;
221 
222             juce::Path p;
223 
224             if (style == juce::Slider::LinearBarVertical)
225                 p.addRectangle (fx, sliderPos, fw, 1.0f + fh - sliderPos);
226             else
227                 p.addRectangle (fx, fy, sliderPos - fx, fh);
228 
229             juce::Colour baseColour (slider.findColour (juce::Slider::rotarySliderFillColourId)
230                                .withMultipliedSaturation (slider.isEnabled() ? 1.0f : 0.5f)
231                                .withMultipliedAlpha (0.8f));
232 
233             g.setColour (baseColour);
234             g.fillPath (p);
235 
236             const float lineThickness = juce::jmin(15.0f, juce::jmin(width, height) * 0.45f) * 0.1f;
237             g.drawRect (slider.getLocalBounds().toFloat(), lineThickness);
238         } else {
239             drawLinearSliderBackground (g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, style, slider);
240             drawLinearSliderThumb (g, x, y, width, height, sliderPos, minSliderPos, maxSliderPos, style, slider);
241         }
242     }
243 
drawLinearSliderBackgroundCustomLookAndFeel244     void drawLinearSliderBackground (juce::Graphics& g, int x, int y, int width, int height,
245                                      float /*sliderPos*/,
246                                      float /*minSliderPos*/,
247                                      float /*maxSliderPos*/,
248                                      const juce::Slider::SliderStyle /*style*/, juce::Slider& slider) override
249     {
250         const float sliderRadius = getSliderThumbRadius (slider) - 5.0f;
251         juce::Path on, off;
252 
253         if (slider.isHorizontal()) {
254             const float iy = y + height * 0.5f - sliderRadius * 0.5f;
255             juce::Rectangle<float> r (x - sliderRadius * 0.5f, iy, width + sliderRadius, sliderRadius);
256             const float onW = r.getWidth() * ((float)slider.valueToProportionOfLength (slider.getValue()));
257 
258             on.addRectangle (r.removeFromLeft (onW));
259             off.addRectangle (r);
260         } else {
261             const float ix = x + width * 0.5f - sliderRadius * 0.5f;
262             juce::Rectangle<float> r (ix, y - sliderRadius * 0.5f, sliderRadius, height + sliderRadius);
263             const float onH = r.getHeight() * ((float)slider.valueToProportionOfLength (slider.getValue()));
264 
265             on.addRectangle (r.removeFromBottom (onH));
266             off.addRectangle (r);
267         }
268 
269         g.setColour (slider.findColour (juce::Slider::rotarySliderFillColourId));
270         g.fillPath (on);
271 
272         g.setColour (slider.findColour (juce::Slider::trackColourId));
273         g.fillPath (off);
274     }
275 
drawRotarySliderCustomLookAndFeel276     void drawRotarySlider (juce::Graphics& g, int x, int y, int width, int height, float sliderPos,
277                            float rotaryStartAngle, float rotaryEndAngle, juce::Slider& slider) override
278     {
279         const float radius = juce::jmin(width / 2, height / 2) - 4.0f;
280         const float centreX = x + width * 0.5f;
281         const float centreY = y + height * 0.5f;
282         const float rx = centreX - radius;
283         const float ry = centreY - radius;
284         const float rw = radius * 2.0f;
285         const float angle = rotaryStartAngle + sliderPos * (rotaryEndAngle - rotaryStartAngle);
286         const bool isMouseOver = slider.isMouseOverOrDragging() && slider.isEnabled();
287 
288         //Background
289         {
290             g.setColour(juce::Colours::lightgrey.withAlpha (isMouseOver ? 1.0f : 0.7f));
291             juce::Path intFilledArc;
292             intFilledArc.addPieSegment(rx, ry, rw, rw, rotaryStartAngle, rotaryEndAngle, 0.8);
293             g.fillPath(intFilledArc);
294         }
295 
296         if (slider.isEnabled()) {
297             g.setColour(slider.findColour (juce::Slider::rotarySliderFillColourId).withAlpha (isMouseOver ? 1.0f : 0.7f));
298         } else {
299             g.setColour(juce::Colour (0x80808080));
300         }
301 
302         //Render knob value
303         {
304             juce::Path pathArc;
305             pathArc.addPieSegment(rx, ry, rw, rw, rotaryStartAngle, angle, 0.8);
306             g.fillPath(pathArc);
307 
308             juce::Path cursor, cursorShadow;
309             float rectWidth = radius*0.4;
310             float rectHeight = rectWidth/2;
311             float rectX = centreX + radius*0.9 - rectHeight/2;
312             float rectY = centreY - rectWidth/2;
313 
314             cursor.addRectangle(rectX, rectY, rectWidth, rectHeight);
315             cursorShadow.addRectangle(rectX-1, rectY-1, rectWidth+2, rectHeight+2);
316 
317             juce::AffineTransform t = juce::AffineTransform::translation(-rectWidth + 2, rectHeight/2);
318             t = t.rotated((angle - juce::float_Pi/2), centreX, centreY);
319 
320             cursor.applyTransform(t);
321             cursorShadow.applyTransform(t);
322 
323             g.setColour(juce::Colours::black);
324             g.fillPath(cursor);
325 
326             g.setColour(juce::Colours::black .withAlpha(0.15f));
327             g.fillPath(cursorShadow);
328         }
329     }
330 };
331 
332 /**
333  * \brief   Different kind of slider available
334  * \see     uiSlider
335  */
336 enum SliderType {
337     HSlider,    /*!< Horizontal Slider      */
338     VSlider,    /*!< Vertical Slider        */
339     NumEntry,   /*!< Numerical Entry Box    */
340     Knob        /*!< Circular Slider        */
341 };
342 
343 /**
344  * \brief   Different kind of VU-meter available.
345  */
346 enum VUMeterType {
347     HVUMeter,   /*!< Horizontal VU-meter    */
348     VVUMeter,   /*!< Vertical VU-meter      */
349     Led,        /*!< LED VU-meter           */
350     NumDisplay  /*!< TextBox VU-meter       */
351 };
352 
353 /**
354  * \brief   Intern class for all FAUST widgets.
355  * \details Every active, passive or box widgets derive from this class.
356  */
357 class uiBase
358 {
359 
360     protected:
361 
362         int fTotalWidth, fTotalHeight;              // Size with margins included (for a uiBox)
363         int fDisplayRectWidth, fDisplayRectHeight;  // Size without margin, just the child dimensions, sum on one dimension, max on the other
364         float fHRatio, fVRatio;
365 
366     public:
367 
368         /**
369          * \brief   Constructor.
370          * \details Initialize a uiBase with all its sizes.
371          *
372          * \param   totWidth    Minimal total width.
373          * \param   totHeight   Minimal total Height.
374          */
375         uiBase(int totWidth = 0, int totHeight = 0):
fTotalWidth(totWidth)376             fTotalWidth(totWidth), fTotalHeight(totHeight),
377             fDisplayRectWidth(0), fDisplayRectHeight(0),
378             fHRatio(1), fVRatio(1)
379         {}
380 
~uiBase()381         virtual ~uiBase()
382         {}
383 
384         /** Return the size. */
getSize()385         juce::Rectangle<int> getSize()
386         {
387             return juce::Rectangle<int>(0, 0, fTotalWidth, fTotalHeight);
388         }
389 
390         /** Return the total height in pixels. */
getTotalHeight()391         int getTotalHeight()
392         {
393             return fTotalHeight;
394         }
395 
396         /** Return the total width in pixels. */
getTotalWidth()397         int getTotalWidth()
398         {
399             return fTotalWidth;
400         }
401 
402         /** Return the horizontal ratio, between 0 and 1. */
getHRatio()403         float getHRatio()
404         {
405             return fHRatio;
406         }
407 
408         /** Return the vertical ratio, between 0 and 1. */
getVRatio()409         float getVRatio()
410         {
411             return fVRatio;
412         }
413 
414         /**
415          * \brief   Set the uiBase bounds.
416          * \details Convert absolute bounds to relative bounds,
417          *          used in JUCE Component mechanics.
418          *
419          * \param r The absolute bounds.
420          *
421          */
setRelativeSize(juce::Component * comp,const juce::Rectangle<int> & r)422         void setRelativeSize(juce::Component* comp, const juce::Rectangle<int>& r)
423         {
424             comp->setBounds(r.getX() - comp->getParentComponent()->getX(),
425                             r.getY() - comp->getParentComponent()->getY(),
426                             r.getWidth(),
427                             r.getHeight());
428         }
429 
430         virtual void init(juce::Component* comp = nullptr)
431         {
432             /** Initialize both vertical and horizontal ratios. */
433             assert(comp);
434             uiBase* parentBox = comp->findParentComponentOfClass<uiBase>();
435             if (parentBox != nullptr) {
436                 fHRatio = (float)fTotalWidth / (float)parentBox->fDisplayRectWidth;
437                 fVRatio = (float)fTotalHeight / (float)parentBox->fDisplayRectHeight;
438             }
439         }
440 
setRecommendedSize()441         virtual void setRecommendedSize()
442         {}
443 
add(juce::Component * comp)444         virtual void add(juce::Component* comp)
445         {}
446 
447 };
448 
449 /**
450  * \brief   Intern class for all FAUST active or passive widgets.
451  * \details Every activ or passive widgets derive from this class.
452  */
453 class uiComponent : public uiBase, public juce::Component, public uiItem
454 {
455 
456     public:
457         /**
458          * \brief   Constructor.
459          * \details Initialize all uiItem, uiBase and the tooltip variables.
460          *
461          * \param   gui     Current FAUST GUI.
462          * \param   zone    Zone of the widget.
463          * \param   w       Width of the widget.
464          * \param   h       Height of the widget.
465          * \param   name    Name of the widget.
466          */
uiComponent(GUI * gui,FAUSTFLOAT * zone,int w,int h,juce::String name)467         uiComponent(GUI* gui, FAUSTFLOAT* zone, int w, int h, juce::String name):uiBase(w, h), Component(name), uiItem(gui, zone)
468         {}
469 
470 };
471 
472 /**
473  * \brief   Intern class for all kind of sliders.
474  * \see     SliderType
475  */
476 class uiSlider : public uiComponent, public uiConverter, private juce::Slider::Listener
477 {
478 
479     private:
480 
481         juce::Slider::SliderStyle fStyle;
482         juce::Label fLabel;
483         SliderType fType;
484         juce::Slider fSlider;
485 
486     public:
487         /**
488          * \brief   Constructor.
489          * \details Initialize all uiComponent variables, and Slider specific ones.
490          *          Initialize juce::Slider parameters.
491          *
492          * \param   gui, zone, w, h, tooltip, name  uiComponent variables.
493          * \param   min                             Minimum value of the slider.
494          * \param   max                             Maximum value of the slider.
495          * \param   cur                             Initial value of the slider.
496          * \param   step                            Step of the slider.
497          * \param   unit                            Unit of the slider value.
498          * \param   scale                           Scale of the slider, exponential, logarithmic, or linear.
499          * \param   type                            Type of slider (see SliderType).
500          */
uiSlider(GUI * gui,FAUSTFLOAT * zone,FAUSTFLOAT w,FAUSTFLOAT h,FAUSTFLOAT cur,FAUSTFLOAT min,FAUSTFLOAT max,FAUSTFLOAT step,juce::String name,juce::String unit,juce::String tooltip,MetaDataUI::Scale scale,SliderType type)501         uiSlider(GUI* gui, FAUSTFLOAT* zone, FAUSTFLOAT w, FAUSTFLOAT h, FAUSTFLOAT cur, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step, juce::String name, juce::String unit, juce::String tooltip, MetaDataUI::Scale scale, SliderType type)
502             : uiComponent(gui, zone, w, h, name), uiConverter(scale, min, max, min, max), fType(type)
503         {
504             // Set the JUCE widget initalization variables.
505             switch(fType) {
506                 case HSlider:
507                     fStyle = juce::Slider::SliderStyle::LinearHorizontal;
508                     break;
509                 case VSlider:
510                     fStyle = juce::Slider::SliderStyle::LinearVertical;
511                     fSlider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 60, 20);
512                     break;
513                 case NumEntry:
514                     fSlider.setIncDecButtonsMode(juce::Slider::incDecButtonsDraggable_AutoDirection);
515                     fStyle = juce::Slider::SliderStyle::IncDecButtons;
516                     break;
517                 case Knob:
518                     fStyle = juce::Slider::SliderStyle::Rotary;
519                     fSlider.setTextBoxStyle(juce::Slider::TextBoxBelow, false, 60, 20);
520                     break;
521                 default:
522                     break;
523             }
524             addAndMakeVisible(fSlider);
525 
526             // Slider settings
527             fSlider.setRange(min, max, step);
528             fSlider.setValue(fConverter->faust2ui(cur));
529             fSlider.addListener(this);
530             fSlider.setSliderStyle(fStyle);
531             fSlider.setTextValueSuffix(" " + unit);
532             fSlider.setTooltip(tooltip);
533             switch (scale) {
534                 case MetaDataUI::kLog:
535                     fSlider.setSkewFactor(0.25);
536                     break;
537                 case MetaDataUI::kExp:
538                     fSlider.setSkewFactor(0.75);
539                     break;
540                 default:
541                     break;
542             }
543 
544             // Label settings, only happens for a horizontal of numerical entry slider
545             // because the method attachToComponent only give the choice to place the
546             // slider name on centered top, which is what we want. It's done manually
547             // in the paint method.
548             if (fType == HSlider || fType == NumEntry) {
549                 fLabel.setText(getName(), juce::dontSendNotification);
550                 fLabel.attachToComponent(&fSlider, true);
551                 fLabel.setTooltip(tooltip);
552                 addAndMakeVisible(fLabel);
553             }
554         }
555 
556         /** Draw the name of a vertical or circular slider. */
paint(juce::Graphics & g)557         virtual void paint(juce::Graphics& g) override
558         {
559             if (fType == VSlider || fType == Knob) {
560                 g.setColour(juce::Colours::black);
561                 g.drawText(getName(), getLocalBounds(), juce::Justification::centredTop);
562             }
563         }
564 
565         /** Allow to control the slider when its value is changed, but not by the user. */
reflectZone()566         void reflectZone() override
567         {
568             FAUSTFLOAT v = *fZone;
569             fCache = v;
570             fSlider.setValue(fConverter->faust2ui(v));
571         }
572 
573         /** JUCE callback for a slider value change, give the value to the FAUST module. */
sliderValueChanged(juce::Slider * slider)574         void sliderValueChanged(juce::Slider* slider) override
575         {
576             float v = slider->getValue();
577             modifyZone(FAUSTFLOAT(fConverter->ui2faust(v)));
578         }
579 
580         /**
581          * Set the good coordinates and size for the juce::Slider object depending
582          * on its SliderType, whenever the layout size changes.
583          */
resized()584         void resized() override
585         {
586             int x, y, width, height;
587 
588             switch (fType) {
589 
590                 case HSlider: {
591                     int nameWidth = juce::Font().getStringWidth(getName()) + kMargin * 2;
592                     x = nameWidth;
593                     y = 0;
594                     width = getWidth() - nameWidth;
595                     height = getHeight();
596                     break;
597                 }
598 
599                 case VSlider:
600                     x = 0;
601                     y = kNameHeight; // kNameHeight pixels for the name
602                     height = getHeight() - kNameHeight;
603                     width = getWidth();
604                     break;
605 
606                 case NumEntry:
607                     width = kNumEntryWidth;
608                     height = kNumEntryHeight;
609                     // x position is the top left corner horizontal position of the box
610                     // and not the top left of the NumEntry label, so we have to do that
611                     x = (getWidth() - width)/2 + (juce::Font().getStringWidth(getName()) + kMargin)/2;
612                     y = (getHeight() - height)/2;
613                     break;
614 
615                 case Knob:
616                     // The knob name needs to be displayed, kNameHeight pixels
617                     height = width = juce::jmin(getHeight() - kNameHeight, kKnobHeight);
618                     x = (getWidth() - width)/2;
619                     // kNameHeight pixels for the knob name still
620                     y = juce::jmax((getHeight() - height)/2, kNameHeight);
621                     break;
622 
623                 default:
624                     assert(false);
625                     break;
626             }
627 
628             fSlider.setBounds(x, y, width, height);
629         }
630 
631 };
632 
633 /** Intern class for button */
634 class uiButton : public uiComponent, private juce::Button::Listener
635 {
636 
637     private:
638 
639         juce::TextButton fButton;
640 
641     public:
642         /**
643          * \brief   Constructor.
644          * \details Initialize all uiComponent variables and juce::TextButton parameters.
645          *
646          * \param   gui, zone, w, h, tooltip, label uiComponent variable.
647          */
uiButton(GUI * gui,FAUSTFLOAT * zone,FAUSTFLOAT w,FAUSTFLOAT h,juce::String label,juce::String tooltip)648         uiButton(GUI* gui, FAUSTFLOAT* zone, FAUSTFLOAT w, FAUSTFLOAT h, juce::String label, juce::String tooltip) :  uiComponent(gui, zone, w, h, label)
649         {
650             int x = 0;
651             int y = (getHeight() - kButtonHeight)/2;
652 
653             fButton.setButtonText(label);
654             fButton.setBounds(x, y, kButtonWidth, kButtonHeight);
655             fButton.addListener(this);
656             fButton.setTooltip(tooltip);
657             addAndMakeVisible(fButton);
658         }
659 
660         /**
661          * Has to be defined because its a pure virtual function of juce::Button::Listener,
662          * which uiButton derives from. Control of user actions is done in buttonStateChanged.
663          * \see buttonStateChanged
664          */
buttonClicked(juce::Button * button)665         void buttonClicked (juce::Button* button) override
666         {}
667 
668         /** Indicate to the FAUST module when the button is pressed and released. */
buttonStateChanged(juce::Button * button)669         void buttonStateChanged (juce::Button* button) override
670         {
671             if (button->isDown()) {
672                 modifyZone(FAUSTFLOAT(1));
673             } else {
674                 modifyZone(FAUSTFLOAT(0));
675             }
676         }
677 
reflectZone()678         void reflectZone() override
679         {
680             FAUSTFLOAT v = *fZone;
681             fCache = v;
682             if (v == FAUSTFLOAT(1)) {
683                 fButton.triggerClick();
684             }
685         }
686 
687         /** Set the good coordinates and size to the juce::TextButton widget whenever the layout size changes. */
resized()688         virtual void resized() override
689         {
690             int x = kMargin;
691             int width = getWidth() - 2 * kMargin;
692             int height = juce::jmin(getHeight(), kButtonHeight);
693             int y = (getHeight()-height)/2;
694             fButton.setBounds(x, y, width, height);
695         }
696 
697 };
698 
699 /** Intern class for checkButton */
700 class uiCheckButton : public uiComponent, private juce::Button::Listener
701 {
702 
703     private:
704 
705         juce::ToggleButton fCheckButton;
706 
707     public:
708         /**
709          * \brief   Constructor.
710          * \details Initialize all uiComponent variables and juce::ToggleButton parameters.
711          *
712          * \param   gui, zone, w, h, label, tooltip  uiComponent variables.
713          */
uiCheckButton(GUI * gui,FAUSTFLOAT * zone,FAUSTFLOAT w,FAUSTFLOAT h,juce::String label,juce::String tooltip)714         uiCheckButton(GUI* gui, FAUSTFLOAT* zone, FAUSTFLOAT w, FAUSTFLOAT h, juce::String label, juce::String tooltip) : uiComponent(gui, zone, w, h, label)
715         {
716             int x = 0;
717             int y = (getHeight()-h)/2;
718 
719             fCheckButton.setButtonText(label);
720             fCheckButton.setBounds(x, y, w, h);
721             fCheckButton.addListener(this);
722             fCheckButton.setTooltip(tooltip);
723             addAndMakeVisible(fCheckButton);
724         }
725 
726         /** Indicate to the FAUST module when the button is toggled or not. */
buttonClicked(juce::Button * button)727         void buttonClicked(juce::Button* button) override
728         {
729             //std::cout << getName() << " : " << button->getToggleState() << std::endl;
730             modifyZone(button->getToggleState());
731         }
732 
reflectZone()733         void reflectZone() override
734         {
735             FAUSTFLOAT v = *fZone;
736             fCache = v;
737             fCheckButton.triggerClick();
738         }
739 
740         /** Set the good coordinates and size to the juce::ToggleButton widget, whenever the layout size changes. */
resized()741         virtual void resized() override
742         {
743             fCheckButton.setBounds(getLocalBounds());
744         }
745 
746 };
747 
748 /** Intern class for Menu */
749 class uiMenu : public uiComponent, private juce::ComboBox::Listener
750 {
751 
752     private:
753 
754         juce::ComboBox fComboBox;
755         std::vector<double> fValues;
756 
757     public:
758         /**
759          * \brief   Constructor.
760          * \details Initialize the uiComponent and Menu specific variables, and the juce::ComboBox parameters.
761          *          Menu is considered as a slider in the FAUST logic, with a step of one. The first item
762          *          would be 0 on a slider, the second 1, etc. Each "slider value" is associated with a
763          *          string.
764          *
765          * \param   gui, zone, w, h, tooltip, label     uiComponent variables.
766          * \param   cur                                 Current "slider value" associated with the current item selected.
767          * \param   low                                 Lowest value possible.
768          * \param   hi                                  Highest value possible.
769          * \param   mdescr                              Menu description. Contains the names of the items associated with their "value".
770          */
uiMenu(GUI * gui,FAUSTFLOAT * zone,juce::String label,FAUSTFLOAT w,FAUSTFLOAT h,FAUSTFLOAT cur,FAUSTFLOAT lo,FAUSTFLOAT hi,juce::String tooltip,const char * mdescr)771         uiMenu(GUI* gui, FAUSTFLOAT* zone, juce::String label, FAUSTFLOAT w, FAUSTFLOAT h, FAUSTFLOAT cur, FAUSTFLOAT lo, FAUSTFLOAT hi, juce::String tooltip, const char* mdescr) : uiComponent(gui, zone, w, h, label)
772         {
773             //Init ComboBox parameters
774             fComboBox.setEditableText(false);
775             fComboBox.setJustificationType(juce::Justification::centred);
776             fComboBox.addListener(this);
777             addAndMakeVisible(fComboBox);
778 
779             std::vector<std::string> names;
780             std::vector<double> values;
781 
782             if (parseMenuList(mdescr, names, values)) {
783 
784                 int defaultitem = -1;
785                 double mindelta = FLT_MAX;
786                 int item = 1;
787 
788                 // Go through all the Menu's items.
789                 for (int i = 0; i < names.size(); i++) {
790                     double v = values[i];
791                     if ((v >= lo) && (v <= hi)) {
792                         // It is a valid value : add corresponding menu item
793                         // item astrating at 1 because index 0 is reserved for a non-defined item.
794                         fComboBox.addItem(juce::String(names[i].c_str()), item++);
795                         fValues.push_back(v);
796 
797                         // Check if this item is a good candidate to represent the current value
798                         double delta = fabs(cur-v);
799                         if (delta < mindelta) {
800                             mindelta = delta;
801                             defaultitem = fComboBox.getNumItems();
802                         }
803                     }
804                 }
805                 // check the best candidate to represent the current value
806                 if (defaultitem > -1) {
807                     fComboBox.setSelectedItemIndex(defaultitem);
808                 }
809             }
810 
811             *fZone = cur;
812         }
813 
814         /** Indicate to the FAUST module when the selected items is changed. */
comboBoxChanged(juce::ComboBox * cb)815         void comboBoxChanged (juce::ComboBox* cb) override
816         {
817             //std::cout << getName( )<< " : " << cb->getSelectedId() - 1 << std::endl;
818             // -1 because of the starting item  at 1 at the initialization
819             modifyZone(fValues[cb->getSelectedId() - 1]);
820         }
821 
reflectZone()822         virtual void reflectZone() override
823         {
824             FAUSTFLOAT v = *fZone;
825             fCache = v;
826 
827             // search closest value
828             int defaultitem = -1;
829             double mindelta = FLT_MAX;
830 
831             for (unsigned int i = 0; i < fValues.size(); i++) {
832                 double delta = fabs(fValues[i]-v);
833                 if (delta < mindelta) {
834                     mindelta = delta;
835                     defaultitem = i;
836                 }
837             }
838             if (defaultitem > -1) {
839                 fComboBox.setSelectedItemIndex(defaultitem);
840             }
841         }
842 
843         /** Set the good coordinates and size to the juce::ComboBox widget whenever the layout get reiszed */
resized()844         virtual void resized() override
845         {
846             fComboBox.setBounds(0, 0 + kMenuHeight/2, getWidth(), kMenuHeight/2);
847         }
848 
849         /** Display the name of the Menu */
paint(juce::Graphics & g)850         virtual void paint(juce::Graphics& g) override
851         {
852             g.setColour(juce::Colours::black);
853             g.drawText(getName(), getLocalBounds().withHeight(getHeight()/2), juce::Justification::centredTop);
854         }
855 
856 };
857 
858 /** Intern class for RadioButton */
859 class uiRadioButton : public uiComponent, private juce::Button::Listener
860 {
861 
862     private:
863 
864         bool fIsVertical;
865         juce::OwnedArray<juce::ToggleButton> fButtons;
866         std::vector<double> fValues;
867 
868     public:
869         /**
870          * \brief   Constructor.
871          * \details Initialize the uiComponent variables, and the RadioButton specific variables
872          *          and parameters. Works in a similar way to the Menu, because it is a special
873          *          kind of sliders in the faust logic.
874          * \see     uiMenu
875          *
876          * \param   gui, zone, tooltip, label   uiComponent variables.
877          * \param   w                           uiComponent variable and width of the RadioButton widget.
878          * \param   h                           uiComponent variable and height of the RadioButton widget.
879          * \param   cur                         Current "value" associated with the item selected.
880          * \param   low                         Lowest "value" possible.
881          * \param   hi                          Highest "value" possible.
882          * \param   vert                        True if vertical, false if horizontal.
883          * \param   names                       Contain the names of the different items.
884          * \param   values                      Contain the "values" of the different items.
885          * \param   fRadioGroupID               RadioButton being multiple CheckButton in JUCE,
886          *                                      we need an ID to know which are linked together.
887          */
uiRadioButton(GUI * gui,FAUSTFLOAT * zone,juce::String label,FAUSTFLOAT w,FAUSTFLOAT h,FAUSTFLOAT cur,FAUSTFLOAT lo,FAUSTFLOAT hi,bool vert,std::vector<std::string> & names,std::vector<double> & values,juce::String tooltip,int radioGroupID)888         uiRadioButton(GUI* gui, FAUSTFLOAT* zone, juce::String label, FAUSTFLOAT w, FAUSTFLOAT h, FAUSTFLOAT cur, FAUSTFLOAT lo, FAUSTFLOAT hi, bool vert, std::vector<std::string>& names, std::vector<double>& values, juce::String tooltip, int radioGroupID) : uiComponent(gui, zone, w, h, label), fIsVertical(vert)
889         {
890             juce::ToggleButton* defaultbutton = 0;
891             double mindelta = FLT_MAX;
892 
893             for (int i = 0; i < names.size(); i++) {
894                 double v = values[i];
895                 if ((v >= lo) && (v <= hi)) {
896 
897                     // It is a valid value included in slider's range
898                     juce::ToggleButton* tb = new juce::ToggleButton(names[i]);
899                     addAndMakeVisible(tb);
900                     tb->setRadioGroupId (radioGroupID);
901                     tb->addListener(this);
902                     tb->setTooltip(tooltip);
903                     fValues.push_back(v);
904                     fButtons.add(tb);
905 
906                     // Check if this item is a good candidate to represent the current value
907                     double delta = fabs(cur-v);
908                     if (delta < mindelta) {
909                         mindelta = delta;
910                         defaultbutton = tb;
911                     }
912                 }
913             }
914             // check the best candidate to represent the current value
915             if (defaultbutton) {
916                 defaultbutton->setToggleState (true, juce::dontSendNotification);
917             }
918         }
919 
reflectZone()920         virtual void reflectZone() override
921         {
922             FAUSTFLOAT v = *fZone;
923             fCache = v;
924 
925             // select closest value
926             int defaultitem = -1;
927             double mindelta = FLT_MAX;
928 
929             for (unsigned int i = 0; i < fValues.size(); i++) {
930                 double delta = fabs(fValues[i]-v);
931                 if (delta < mindelta) {
932                     mindelta = delta;
933                     defaultitem = i;
934                 }
935             }
936             if (defaultitem > -1) {
937                 fButtons.operator[](defaultitem)->setToggleState (true, juce::dontSendNotification);
938             }
939         }
940 
941         /** Handle the placement of each juce::ToggleButton everytime the layout size is changed. */
resized()942         virtual void resized() override
943         {
944             int width, height;
945             fIsVertical ? (height = (getHeight() - kNameHeight) / fButtons.size()) : (width = getWidth() / fButtons.size());
946 
947             for (int i = 0; i < fButtons.size(); i++) {
948                 if (fIsVertical) {
949                     fButtons.operator[](i)->setBounds(0, i * height + kNameHeight, getWidth(), height);
950                 } else {
951                     // kNameHeight pixels offset for the title
952                     fButtons.operator[](i)->setBounds(i * width, kNameHeight, width, getHeight() - kNameHeight);
953                 }
954             }
955         }
956 
957         /** Display the RadioButton name */
paint(juce::Graphics & g)958         virtual void paint(juce::Graphics& g) override
959         {
960             g.setColour(juce::Colours::black);
961             g.drawText(getName(), getLocalBounds().withHeight(kNameHeight), juce::Justification::centredTop);
962         }
963 
964         /** Check which button is checked, and give its "value" to the FAUST module */
buttonClicked(juce::Button * button)965     void buttonClicked(juce::Button* button) override
966         {
967             juce::ToggleButton* checkButton = dynamic_cast<juce::ToggleButton*>(button);
968             //std::cout << getName() << " : " << fButtons.indexOf(checkButton) << std::endl;
969             modifyZone(fButtons.indexOf(checkButton));
970         }
971 
972 };
973 
974 /**
975  * \brief   Intern class for VU-meter
976  * \details There is no JUCE widgets for VU-meter, so its fully designed in this class.
977  */
978 class uiVUMeter : public uiComponent, public juce::SettableTooltipClient, public juce::Timer
979 {
980 
981     private:
982 
983         FAUSTFLOAT fLevel;               // Current level of the VU-meter.
984         FAUSTFLOAT fMin, fMax;           // Linear range of the VU-meter.
985         FAUSTFLOAT fScaleMin, fScaleMax; // Range in dB if needed.
986         bool fDB;                        // True if it's a dB VU-meter, false otherwise.
987         VUMeterType fStyle;
988         juce::String fUnit;
989         juce::Label fLabel;               // Name of the VU-meter.
990 
isNameDisplayed()991         bool isNameDisplayed()
992         {
993             return (!(getName().startsWith("0x")) && getName().isNotEmpty());
994         }
995 
996         /** Give the right coordinates and size to the text of Label depending on the VU-meter style */
setLabelPos()997         void setLabelPos()
998         {
999             if (fStyle == VVUMeter) {
1000                 // -22 on the height because of the text box.
1001                 fLabel.setBounds((getWidth()-50)/2, getHeight()-22, 50, 20);
1002             } else if (fStyle == HVUMeter) {
1003                 isNameDisplayed() ? fLabel.setBounds(63, (getHeight()-20)/2, 50, 20)
1004                 : fLabel.setBounds(3, (getHeight()-20)/2, 50, 20);
1005             } else if (fStyle == NumDisplay) {
1006                 fLabel.setBounds((getWidth()-kNumDisplayWidth)/2,
1007                                  (getHeight()-kNumDisplayHeight/2)/2,
1008                                  kNumDisplayWidth,
1009                                  kNumDisplayHeight/2);
1010             }
1011         }
1012 
1013         /** Contain all the initialization need for our Label */
setupLabel(juce::String tooltip)1014         void setupLabel(juce::String tooltip)
1015         {
1016             setLabelPos();
1017             fLabel.setEditable(false, false, false);
1018             fLabel.setJustificationType(juce::Justification::centred);
1019             fLabel.setText(juce::String((int)*fZone) + " " + fUnit, juce::dontSendNotification);
1020             fLabel.setTooltip(tooltip);
1021             addAndMakeVisible(fLabel);
1022         }
1023 
1024         /**
1025          * \brief   Generic method to draw an horizontal VU-meter.
1026          * \details Draw the background of the bargraph, and the TextBox box, without taking
1027          *          care of the actual level of the VU-meter
1028          * \see     drawHBargraphDB
1029          * \see     drawHBargraphLin
1030          *
1031          * \param   g       JUCE graphics context, used to draw components or images.
1032          * \param   width   Width of the VU-meter widget.
1033          * \param   height  Height of the VU-meter widget.
1034          * \param   level   Current level that needs to be displayed.
1035          * \param   dB      True if it's a db level, false otherwise.
1036          */
drawHBargraph(juce::Graphics & g,int width,int height)1037         void drawHBargraph(juce::Graphics& g, int width, int height)
1038         {
1039             float x;
1040             float y = (float)(getHeight()-height)/2;
1041             if (isNameDisplayed()) {
1042                 x = 120;
1043                 width -= x;
1044                 // VUMeter Name
1045                 g.setColour(juce::Colours::black);
1046                 g.drawText(getName(), 0, y, 60, height, juce::Justification::centredRight);
1047             } else {
1048                 x = 60;
1049                 width -= x;
1050             }
1051 
1052             // VUMeter Background
1053             g.setColour(juce::Colours::lightgrey);
1054             g.fillRect(x, y, (float)width, (float)height);
1055             g.setColour(juce::Colours::black);
1056             g.fillRect(x+1.0f, y+1.0f, (float)width-2, (float)height-2);
1057 
1058             // Label Window
1059             g.setColour(juce::Colours::darkgrey);
1060             g.fillRect((int)x-58, (getHeight()-22)/2, 52, 22);
1061             g.setColour(juce::Colours::white.withAlpha(0.8f));
1062             g.fillRect((int)x-57, (getHeight()-20)/2, 50, 20);
1063 
1064             // Call the appropriate drawing method for the level.
1065             fDB ? drawHBargraphDB (g, y, height) : drawHBargraphLin(g, x, y, width, height);
1066         }
1067 
1068         /**
1069          * Method in charge of drawing the level of a horizontal dB VU-meter.
1070          *
1071          * \param   g       JUCE graphics context, used to draw components or images.
1072          * \param   y       y coordinate of the VU-meter.
1073          * \param   height  Height of the VU-meter.
1074          * \param   level   Current level of the VU-meter, in dB.
1075          */
drawHBargraphDB(juce::Graphics & g,int y,int height)1076         void drawHBargraphDB(juce::Graphics& g, int y, int height)
1077         {
1078             // Drawing Scale
1079             g.setFont(9.0f);
1080             g.setColour(juce::Colours::white);
1081             for (int i = -10; i > fMin; i -= 10) {
1082                 paintScale(g, i);
1083             }
1084             for (int i = -6; i < fMax; i += 3)  {
1085                 paintScale(g, i);
1086             }
1087 
1088             int alpha = 200;
1089             FAUSTFLOAT dblevel = dB2Scale(fLevel);
1090 
1091             // We need to test here every color changing levels, to avoid to mix colors because of the alpha,
1092             // and so to start the new color rectangle at the end of the previous one.
1093 
1094             // Drawing from the minimal range to the current level, or -10dB.
1095             g.setColour(juce::Colour((juce::uint8)40, (juce::uint8)160, (juce::uint8)40, (juce::uint8)alpha));
1096             g.fillRect(dB2x(fMin), y+1.0f, juce::jmin(dB2x(fLevel)-dB2x(fMin), dB2x(-10)-dB2x(fMin)), (float)height-2);
1097 
1098             // Drawing from -10dB to the current level, or -6dB.
1099             if (dblevel > dB2Scale(-10)) {
1100                 g.setColour(juce::Colour((juce::uint8)160, (juce::uint8)220, (juce::uint8)20, (juce::uint8)alpha));
1101                 g.fillRect(dB2x(-10), y+1.0f, juce::jmin(dB2x(fLevel)-dB2x(-10), dB2x(-6)-dB2x(-10)), (float)height-2);
1102             }
1103             // Drawing from -6dB to the current level, or -3dB.
1104             if (dblevel > dB2Scale(-6)) {
1105                 g.setColour(juce::Colour((juce::uint8)220, (juce::uint8)220, (juce::uint8)20, (juce::uint8)alpha));
1106                 g.fillRect(dB2x(-6), y+1.0f, juce::jmin(dB2x(fLevel)-dB2x(-6), dB2x(-3)-dB2x(-6)), (float)height-2);
1107             }
1108             // Drawing from -3dB to the current level, or 0dB.
1109             if (dblevel > dB2Scale(-3)) {
1110                 g.setColour(juce::Colour((juce::uint8)240, (juce::uint8)160, (juce::uint8)20, (juce::uint8)alpha));
1111                 g.fillRect(dB2x(-3), y+1.0f, juce::jmin(dB2x(fLevel)-dB2x(-3), dB2x(0)-dB2x(-3)), (float)height-2);
1112             }
1113             // Drawing from 0dB to the current level, or the max range.
1114             if (dblevel > dB2Scale(0)) {
1115                 g.setColour(juce::Colour((juce::uint8)240, (juce::uint8)0, (juce::uint8)20, (juce::uint8)alpha));
1116                 g.fillRect(dB2x(0), y+1.0f, juce::jmin(dB2x(fLevel)-dB2x(0), dB2x(fMax)-dB2x(0)), (float)height-2);
1117             }
1118         }
1119 
1120         /**
1121          * Method in charge of drawing the level of a horizontal linear VU-meter.
1122          *
1123          * \param   g       JUCE graphics context, used to draw components or images.
1124          * \param   x       x coordinate of the VU-meter.
1125          * \param   y       y coordinate of the VU-meter.
1126          * \param   height  Height of the VU-meter.
1127          * \param   width   Width of the VU-meter.
1128          * \param   level   Current level of the VU-meter, in linear logic.
1129          */
drawHBargraphLin(juce::Graphics & g,int x,int y,int width,int height)1130         void drawHBargraphLin(juce::Graphics& g, int x, int y, int width, int height)
1131         {
1132             int alpha = 200;
1133             juce::Colour c = juce::Colour((juce::uint8)255, (juce::uint8)165, (juce::uint8)0, (juce::uint8)alpha);
1134 
1135             // Drawing from the minimal range to the current level, or 20% of the VU-meter
1136             g.setColour(c.brighter());
1137             g.fillRect(x+1.0f, y+1.0f, juce::jmin<float>(fLevel*(width-2), 0.2f*(width-2)), (float)height-2);
1138             // Drawing from 20% of the VU-meter to the current level, or 90% of the VU-meter
1139             if (fLevel > 0.2f) {
1140                 g.setColour(c);
1141                 g.fillRect(x+1.0f + 0.2f*(width-2), y+1.0f, juce::jmin<float>((fLevel-0.2f) * (width-2), (0.9f-0.2f) * (width-2)), (float)height-2);
1142             }
1143             // Drawing from 90% of the VU-meter to the current level, or the maximal range of the VU-meter
1144             if (fLevel > 0.9f) {
1145                 g.setColour(c.darker());
1146                 g.fillRect(x+1.0f + 0.9f*(width-2), y+1.0f, juce::jmin<float>((fLevel-0.9f) * (width-2), (1.0f-0.9f) * (width-2)), (float)height-2);
1147             }
1148         }
1149         /**
1150          * \brief   Generic method to draw a vertical VU-meter.
1151          * \details Draw the background of the bargraph, and the TextBox box, without taking
1152          *          care of the actual level of the VU-meter
1153          * \see     drawHBargraphDB
1154          * \see     drawHBargraphLin
1155          *
1156          * \param   g       JUCE graphics context, used to draw components or images.
1157          * \param   width   Width of the VU-meter widget.
1158          * \param   height  Height of the VU-meter widget.
1159          * \param   level   Current level that needs to be displayed.
1160          * \param   dB      True if it's a db level, false otherwise.
1161          */
drawVBargraph(juce::Graphics & g,int width,int height)1162         void drawVBargraph(juce::Graphics& g, int width, int height)
1163         {
1164             float x = (float)(getWidth()-width)/2;
1165             float y;
1166             if (isNameDisplayed()) {
1167                 y = (float)getHeight()-height+15;
1168                 height -= 40;
1169                 // VUMeter Name
1170                 g.setColour(juce::Colours::black);
1171                 g.drawText(getName(), getLocalBounds(), juce::Justification::centredTop);
1172             } else {
1173                 y = (float)getHeight()-height;
1174                 height -= 25;
1175             }
1176 
1177             // VUMeter Background
1178             g.setColour(juce::Colours::lightgrey);
1179             g.fillRect(x, y, (float)width, (float)height);
1180             g.setColour(juce::Colours::black);
1181             g.fillRect(x+1.0f, y+1.0f, (float)width-2, (float)height-2);
1182 
1183             // Label window
1184             g.setColour(juce::Colours::darkgrey);
1185             g.fillRect(juce::jmax((getWidth()-50)/2, 0), getHeight()-23, juce::jmin(getWidth(), 50), 22);
1186             g.setColour(juce::Colours::white.withAlpha(0.8f));
1187             g.fillRect(juce::jmax((getWidth()-48)/2, 1), getHeight()-22, juce::jmin(getWidth()-2, 48), 20);
1188 
1189             fDB ? drawVBargraphDB (g, x, width) : drawVBargraphLin(g, x, width);
1190         }
1191 
1192         /**
1193          * Method in charge of drawing the level of a vertical dB VU-meter.
1194          *
1195          * \param   g       JUCE graphics context, used to draw components or images.
1196          * \param   x       x coordinate of the VU-meter.
1197          * \param   width   Width of the VU-meter.
1198          * \param   level   Current level of the VU-meter, in dB.
1199          */
drawVBargraphDB(juce::Graphics & g,int x,int width)1200         void drawVBargraphDB(juce::Graphics& g, int x, int width)
1201         {
1202             // Drawing Scale
1203             g.setFont(9.0f);
1204             g.setColour(juce::Colours::white);
1205             for (int i = -10; i > fMin; i -= 10) {
1206                 paintScale(g, i);
1207             }
1208             for (int i = -6; i < fMax; i += 3)  {
1209                 paintScale(g, i);
1210             }
1211 
1212             int alpha = 200;
1213             FAUSTFLOAT dblevel = dB2Scale(fLevel);
1214 
1215             // We need to test here every color changing levels, to avoid to mix colors because of the alpha,
1216             // and so to start the new color rectangle at the end of the previous one.
1217 
1218             // Drawing from the minimal range to the current level, or -10dB.
1219             g.setColour(juce::Colour((juce::uint8)40, (juce::uint8)160, (juce::uint8)40, (juce::uint8)alpha));
1220             g.fillRect(x+1.0f, juce::jmax(dB2y(fLevel), dB2y(-10)), (float)width-2, dB2y(fMin)-juce::jmax(dB2y(fLevel), dB2y(-10)));
1221 
1222             // Drawing from -10dB to the current level, or -6dB.
1223             if (dblevel > dB2Scale(-10)) {
1224                 g.setColour(juce::Colour((juce::uint8)160, (juce::uint8)220, (juce::uint8)20, (juce::uint8)alpha));
1225                 g.fillRect(x+1.0f, juce::jmax(dB2y(fLevel), dB2y(-6)), (float)width-2, dB2y(-10)-juce::jmax(dB2y(fLevel), dB2y(-6)));
1226             }
1227             // Drawing from -6dB to the current level, or -3dB.
1228             if (dblevel > dB2Scale(-6)) {
1229                 g.setColour(juce::Colour((juce::uint8)220, (juce::uint8)220, (juce::uint8)20, (juce::uint8)alpha));
1230                 g.fillRect(x+1.0f, juce::jmax(dB2y(fLevel), dB2y(-3)), (float)width-2, dB2y(-6)-juce::jmax(dB2y(fLevel), dB2y(-3)));
1231             }
1232             // Drawing from -3dB to the current level, or 0dB.
1233             if (dblevel > dB2Scale(-3)) {
1234                 g.setColour(juce::Colour((juce::uint8)240, (juce::uint8)160, (juce::uint8)20, (juce::uint8)alpha));
1235                 g.fillRect(x+1.0f, juce::jmax(dB2y(fLevel), dB2y(0)), (float)width-2, dB2y(-3)-juce::jmax(dB2y(fLevel), dB2y(0)));
1236             }
1237             // Drawing from 0dB to the current level, or the maximum range.
1238             if (dblevel > dB2Scale(0)) {
1239                 g.setColour(juce::Colour((juce::uint8)240, (juce::uint8)0, (juce::uint8)20, (juce::uint8)alpha));
1240                 g.fillRect(x+1.0f, juce::jmax(dB2y(fLevel), dB2y(fMax)), (float)width-2, dB2y(0)-juce::jmax(dB2y(fLevel), dB2y(fMax)));
1241             }
1242         }
1243 
1244         /**
1245          * Method in charge of drawing the level of a vertical linear VU-meter.
1246          *
1247          * \param   g       JUCE graphics context, used to draw components or images.
1248          * \param   x       x coordinate of the VU-meter.
1249          * \param   width   Width of the VU-meter.
1250          * \param   level   Current level of the VU-meter, in linear logic.
1251          */
drawVBargraphLin(juce::Graphics & g,int x,int width)1252         void drawVBargraphLin(juce::Graphics& g, int x, int width)
1253         {
1254             int alpha = 200;
1255             juce::Colour c = juce::Colour((juce::uint8)255, (juce::uint8)165, (juce::uint8)0, (juce::uint8)alpha);
1256 
1257             // Drawing from the minimal range to the current level, or 20% of the VU-meter.
1258             g.setColour(c.brighter());
1259             g.fillRect(x+1.0f, juce::jmax(lin2y(fLevel), lin2y(0.2)), (float)width-2, lin2y(fMin)-juce::jmax(lin2y(fLevel), lin2y(0.2)));
1260 
1261             // Drawing from 20% of the VU-meter to the current level, or 90% of the VU-meter.
1262             if (fLevel > 0.2f) {
1263                 g.setColour(c);
1264                 g.fillRect(x+1.0f, juce::jmax(lin2y(fLevel), lin2y(0.9)), (float)width-2, lin2y(0.2)-juce::jmax(lin2y(fLevel), lin2y(0.9)));
1265             }
1266 
1267             // Drawing from 90% of the VU-meter to the current level, or the maximum range.
1268             if (fLevel > 0.9f) {
1269                 g.setColour(c.darker());
1270                 g.fillRect(x+1.0f, juce::jmax(lin2y(fLevel), lin2y(fMax)), (float)width-2, lin2y(0.9)-juce::jmax(lin2y(fLevel), lin2y(fMax)));
1271             }
1272         }
1273 
1274         /**
1275          * Method in charge of drawing the LED VU-meter, dB or not.
1276          *
1277          * \param   g       JUCE graphics context, used to draw components or images.
1278          * \param   width   Width of the LED.
1279          * \param   height  Height of the LED.
1280          * \param   level   Current level of the VU-meter, dB or not.
1281          */
drawLed(juce::Graphics & g,int width,int height)1282         void drawLed(juce::Graphics& g, int width, int height)
1283         {
1284             float x = (float)(getWidth() - width)/2;
1285             float y = (float)(getHeight() - height)/2;
1286             g.setColour(juce::Colours::black);
1287             g.fillEllipse(x, y, width, height);
1288 
1289             if (fDB) {
1290                 int alpha = 200;
1291                 FAUSTFLOAT dblevel = dB2Scale(fLevel);
1292 
1293                 // Adjust the color depending on the current level
1294                 g.setColour(juce::Colour((juce::uint8)40, (juce::uint8)160, (juce::uint8)40, (juce::uint8)alpha));
1295                 if (dblevel > dB2Scale(-10)) {
1296                     g.setColour(juce::Colour((juce::uint8)160, (juce::uint8)220, (juce::uint8)20, (juce::uint8)alpha));
1297                 }
1298                 if (dblevel > dB2Scale(-6)) {
1299                     g.setColour(juce::Colour((juce::uint8)220, (juce::uint8)220, (juce::uint8)20, (juce::uint8)alpha));
1300                 }
1301                 if (dblevel > dB2Scale(-3)) {
1302                     g.setColour(juce::Colour((juce::uint8)240, (juce::uint8)160, (juce::uint8)20, (juce::uint8)alpha));
1303                 }
1304                 if (dblevel > dB2Scale(0))  {
1305                     g.setColour(juce::Colour((juce::uint8)240, (juce::uint8)0, (juce::uint8)20, (juce::uint8)alpha));
1306                 }
1307 
1308                 g.fillEllipse(x+1, y+1, width-2, height-2);
1309             } else {
1310                 // The alpha depend on the level, from 0 to 1
1311                 g.setColour(juce::Colours::red.withAlpha((float)fLevel));
1312                 g.fillEllipse(x+1, y+1, width-2, height-2);
1313             }
1314         }
1315 
1316         /**
1317          * Method in charge of drawing the Numerical Display VU-meter, dB or not.
1318          *
1319          * \param   g       JUCE graphics context, used to draw components or images.
1320          * \param   width   Width of the Numerical Display.
1321          * \param   height  Height of the Numerical Display.
1322          * \param   level   Current level of the VU-meter.
1323          */
drawNumDisplay(juce::Graphics & g,int width,int height)1324         void drawNumDisplay(juce::Graphics& g, int width, int height)
1325         {
1326             // Centering it
1327             int x = (getWidth()-width) / 2;
1328             int y = (getHeight()-height) / 2;
1329 
1330             // Draw box.
1331             g.setColour(juce::Colours::darkgrey);
1332             g.fillRect(x, y, width, height);
1333             g.setColour(juce::Colours::white.withAlpha(0.8f));
1334             g.fillRect(x+1, y+1, width-2, height-2);
1335 
1336             // Text is handled by the setLabelPos() function
1337         }
1338 
1339         /** Convert a dB level to a y coordinate, for easier draw methods. */
dB2y(FAUSTFLOAT dB)1340         FAUSTFLOAT dB2y(FAUSTFLOAT dB)
1341         {
1342             FAUSTFLOAT s0 = fScaleMin;      // Minimal range.
1343             FAUSTFLOAT s1 = fScaleMax;      // Maximum range.
1344             FAUSTFLOAT sx = dB2Scale(dB);   // Current level.
1345 
1346             int h;
1347             int treshold;   // Value depend if the name is displayed
1348 
1349             if (isNameDisplayed()) {
1350                 h = getHeight()-42; // 15 pixels for the VU-Meter name,
1351                 // 25 for the textBox, 2 pixels margin.
1352                 treshold = 16;      // 15 pixels for the VU-Meter name.
1353             } else {
1354                 h = getHeight()-27; // 25 for the textBox, 2 pixels margin.
1355                 treshold = 1;       // 1 pixel margin.
1356             }
1357 
1358             return (h - h*(s0-sx)/(s0-s1)) + treshold;
1359         }
1360 
1361         /** Convert a linear level to a y coordinate, for easier draw methods. */
lin2y(FAUSTFLOAT level)1362         FAUSTFLOAT lin2y(FAUSTFLOAT level)
1363         {
1364             int h;
1365             int treshold;
1366 
1367             if (isNameDisplayed()) {
1368                 h = getHeight()-42; // 15 pixels for the VU-Meter name,
1369                 // 25 for the textBox, 2 pixels margin.
1370                 treshold = 16;      // 15 pixels for the VU-Meter name.
1371             } else {
1372                 h = getHeight()-27; // 25 for the textBox, 2 pixels margin.
1373                 treshold = 1;       // 1 pixel margin.
1374             }
1375 
1376             return h * (1 - level) + treshold;
1377         }
1378 
1379         /** Convert a dB level to a x coordinate, for easier draw methods. */
dB2x(FAUSTFLOAT dB)1380         FAUSTFLOAT dB2x(FAUSTFLOAT dB)
1381         {
1382             FAUSTFLOAT s0 = fScaleMin;      // Minimal range.
1383             FAUSTFLOAT s1 = fScaleMax;      // Maximal range.
1384             FAUSTFLOAT sx = dB2Scale(dB);   // Current level.
1385 
1386             int w;
1387             int treshold;
1388 
1389             if (isNameDisplayed()) {
1390                 w = getWidth()-122; // 60 pixels for the VU-Meter name,
1391                 // 60 for the TextBox, 2 pixels margin.
1392                 treshold = 121;     // 60 pixels for the VU-Meter name,
1393                 // 60 for the TextBox, and 1 pixel margin.
1394             } else {
1395                 w = getWidth()-62;  // 60 pixels for the TextBox, 2 pixels margin.
1396                 treshold = 61;      // 60 pixels for the TextBox, 1 pixel margin.
1397             }
1398 
1399             return treshold + w - w*(s1-sx)/(s1-s0);
1400         }
1401 
1402         /** Write the different level included in the VU-Meter range. */
paintScale(juce::Graphics & g,float num)1403         void paintScale(juce::Graphics& g, float num)
1404         {
1405             juce::Rectangle<int> r;
1406 
1407             if (fStyle == VVUMeter) {
1408                 r = juce::Rectangle<int>((getWidth()-(kVBargraphWidth/2))/2 + 1,  // Left side of the VU-Meter.
1409                                          dB2y(num),                               // Vertically centred with 20 height.
1410                                          (kVBargraphWidth/2)-2,                   // VU-Meter width with margin.
1411                                          20);                                     // 20 height.
1412                 g.drawText(juce::String(num), r, juce::Justification::centredRight, false);
1413             } else {
1414                 r = juce::Rectangle<int>(dB2x(num)-10,                            // Horizontally centred with 20 width.
1415                                         (getHeight()-kHBargraphHeight/2)/2 + 1,  // Top side of the VU-Meter.
1416                                         20,                                      // 20 width.
1417                                         (kHBargraphHeight/2)-2);                 // VU-Meter height with margin
1418                 g.drawText(juce::String(num), r, juce::Justification::centredTop, false);
1419             }
1420         }
1421 
1422         /** Set the level, keep it in the range of the VU-Meter, and set the TextBox text. */
setLevel()1423         void setLevel()
1424         {
1425             FAUSTFLOAT rawLevel = *fZone;
1426         #if JUCE_DEBUG
1427             if (std::isnan(rawLevel)) {
1428                 std::cerr << "uiVUMeter: NAN\n";
1429             }
1430         #endif
1431             if (fDB) {
1432                 fLevel = range(rawLevel);
1433             } else {
1434                 fLevel = range((rawLevel-fMin)/(fMax-fMin));
1435             }
1436             fLabel.setText(juce::String((int)rawLevel) + " " + fUnit, juce::dontSendNotification);
1437         }
1438 
range(FAUSTFLOAT level)1439         FAUSTFLOAT range(FAUSTFLOAT level) { return (level > fMax) ? fMax : ((level < fMin) ? fMin : level); }
1440 
1441     public:
1442 
1443         /**
1444          * \brief   Constructor.
1445          * \details Initialize the uiComponent variables and the VU-meter specific ones.
1446          *
1447          * \param   gui, zone, w, h, tooltip, label     uiComponent variables.
1448          * \param   mini                                Minimal value of the VU-meter range.
1449          * \param   maxi                                Maximal value of the VU-meter range.
1450          * \param   unit                                Unit of the VU-meter (dB or not).
1451          * \param   style                               Type of the VU-meter (see VUMeterType).
1452          * \param   vert                                True if vertical, false if horizontal.
1453          */
uiVUMeter(GUI * gui,FAUSTFLOAT * zone,FAUSTFLOAT w,FAUSTFLOAT h,juce::String label,FAUSTFLOAT mini,FAUSTFLOAT maxi,juce::String unit,juce::String tooltip,VUMeterType style,bool vert)1454         uiVUMeter (GUI* gui, FAUSTFLOAT* zone, FAUSTFLOAT w, FAUSTFLOAT h, juce::String label, FAUSTFLOAT mini, FAUSTFLOAT maxi, juce::String unit, juce::String tooltip, VUMeterType style, bool vert)
1455             : uiComponent(gui, zone, w, h, label), fMin(mini), fMax(maxi), fStyle(style)
1456         {
1457             fLevel = 0;         // Initialization of the level
1458             startTimer(50);     // Launch a timer that trigger a callback every 50ms
1459             this->fUnit = unit;
1460             fDB = (unit == "dB");
1461 
1462             if (fDB) {
1463                 // Conversion in dB of the range
1464                 fScaleMin = dB2Scale(fMin);
1465                 fScaleMax = dB2Scale(fMax);
1466             }
1467             setTooltip(tooltip);
1468 
1469             // No text editor for LEDs
1470             if (fStyle != Led) {
1471                 setupLabel(tooltip);
1472             }
1473         }
1474 
1475         /** Method called by the timer every 50ms, to refresh the VU-meter if it needs to */
timerCallback()1476         void timerCallback() override
1477         {
1478             if (isShowing()) {
1479                 //Force painting at the initialisation
1480                 bool forceRepaint = (fLevel == 0);
1481                 FAUSTFLOAT lastLevel = fLevel;   //t-1
1482                 setLevel(); //t
1483 
1484                 // Following condition means that we're repainting our VUMeter only if
1485                 // there's one or more changing pixels between last state and this one,
1486                 // and if the curent level is included in the VUMeter range. It improves
1487                 // performances a lot in IDLE. It's the same for the other style of VUMeter
1488 
1489                 if (fDB) {
1490                     switch (fStyle) {
1491                         case VVUMeter:
1492                             if (((int)dB2y(lastLevel) != (int)dB2y(fLevel) && fLevel >= fMin && fLevel <= fMax) || forceRepaint) {
1493                                 repaint();
1494                             }
1495                             break;
1496                         case HVUMeter:
1497                             if (((int)dB2x(lastLevel) != (int)dB2x(fLevel) && fLevel >= fMin && fLevel <= fMax) || forceRepaint) {
1498                                 repaint();
1499                             }
1500                             break;
1501                         case NumDisplay:
1502                             if (((int)lastLevel != (int)fLevel && fLevel >= fMin && fLevel <= fMax) || forceRepaint) {
1503                                 repaint();
1504                             }
1505                             break;
1506                         case Led:
1507                             if ((dB2Scale(lastLevel) != dB2Scale(fLevel) && fLevel >= fMin && fLevel <= fMax) || forceRepaint) {
1508                                 repaint();
1509                             }
1510                             break;
1511                         default:
1512                             break;
1513                     }
1514                 } else {
1515                     switch (fStyle) {
1516                         case VVUMeter:
1517                             if (((int)lin2y(lastLevel) != (int)lin2y(fLevel) && fLevel >= fMin && fLevel <= fMax) || forceRepaint) {
1518                                 repaint();
1519                             }
1520                             break;
1521                         case HVUMeter:
1522                             if ((std::abs(lastLevel-fLevel) > 0.01 && fLevel >= fMin && fLevel <= fMax) || forceRepaint) {
1523                                 repaint();
1524                             }
1525                             break;
1526                         case NumDisplay:
1527                             if ((std::abs(lastLevel-fLevel) > 0.01 && fLevel >= fMin && fLevel <= fMax) || forceRepaint) {
1528                                 repaint();
1529                             }
1530                             break;
1531                         case Led:
1532                             if (((int)lastLevel != (int)fLevel && fLevel >= fMin && fLevel <= fMax) || forceRepaint) {
1533                                 repaint();
1534                             }
1535                             break;
1536                         default:
1537                             break;
1538                     }
1539                 }
1540             } else {
1541                 fLevel = 0;
1542             }
1543         }
1544 
1545         /**
1546          * Call the appropriate drawing method according to the VU-meter style
1547          * \see drawLed
1548          * \see drawNumDisplay
1549          * \see drawVBargraph
1550          * \see drawHBargraph
1551          */
paint(juce::Graphics & g)1552         void paint(juce::Graphics& g) override
1553         {
1554             switch (fStyle) {
1555                 case Led:
1556                     drawLed(g, kLedWidth, kLedHeight);
1557                     break;
1558                 case NumDisplay:
1559                     drawNumDisplay(g, kNumDisplayWidth, kNumDisplayHeight/2);
1560                     break;
1561                 case VVUMeter:
1562                     drawVBargraph(g, kVBargraphWidth/2, getHeight());
1563                     break;
1564                 case HVUMeter:
1565                     drawHBargraph(g, getWidth(), kHBargraphHeight/2);
1566                     break;
1567                 default:
1568                     break;
1569             }
1570         }
1571 
1572         /** Set the Label position whenever the layout size changes. */
resized()1573         void resized() override
1574         {
1575             setLabelPos();
1576         }
1577 
reflectZone()1578         void reflectZone() override
1579         {
1580             FAUSTFLOAT v = *fZone;
1581             fCache = v;
1582         }
1583 
1584 };
1585 
1586 /** Intern class for tab widget */
1587 class uiTabBox : public uiBase, public juce::TabbedComponent
1588 {
1589 
1590 public:
1591     /**
1592      * \brief   Constructor.
1593      * \details Initalize the juce::TabbedComponent tabs to be at top, and the uiTabBox size at 0
1594      */
uiTabBox()1595     uiTabBox():uiBase(),juce::TabbedComponent(juce::TabbedButtonBar::TabsAtTop)
1596     {}
1597 
1598     /**
1599      * Initialize all his child ratios (1 uiBox per tabs), the LookAndFeel
1600      * and the uiTabBox size to fit the biggest of its child.
1601      */
1602     void init(juce::Component* comp = nullptr) override
1603     {
1604         for (int i = 0; i < getNumTabs(); i++) {
1605             Component* comp = getTabContentComponent(i);
1606             uiBase* base_comp = dynamic_cast<uiBase*>(comp);
1607             base_comp->init(comp);
1608 
1609             // The TabbedComponent size should be as big as its bigger child's dimension, done here
1610             fTotalWidth = juce::jmax(fTotalWidth, base_comp->getTotalWidth());
1611             fTotalHeight = juce::jmax(fTotalHeight, base_comp->getTotalHeight());
1612         }
1613 
1614         fTotalHeight += 30;  // 30 height for the TabBar.
1615     }
1616 
setRecommendedSize()1617     void setRecommendedSize() override
1618     {
1619         for (int i = 0; i < getNumTabs(); i++) {
1620             uiBase* comp = dynamic_cast<uiBase*>(getTabContentComponent(i));
1621             comp->setRecommendedSize();
1622 
1623             // The TabbedComponent size should be as big as its bigger child's dimension, done here
1624             fTotalWidth = juce::jmax(fTotalWidth, comp->getTotalWidth());
1625             fTotalHeight = juce::jmax(fTotalHeight, comp->getTotalHeight());
1626         }
1627 
1628         fTotalHeight += 30;  // 30 height for the TabBar
1629     }
1630 
add(Component * comp)1631     void add(Component* comp) override
1632     {
1633         // Name of the component is moved in Tab (so removed from component)
1634         juce::TabbedComponent::addTab(comp->getName(), juce::Colours::white, comp, true);
1635         comp->setName("");
1636     }
1637 
1638 };
1639 
1640 /**
1641  * \brief   Intern class for box widgets
1642  * \details That's the class where the whole layout is calculated.
1643  */
1644 class uiBox : public uiBase, public juce::Component
1645 {
1646 
1647     private:
1648 
1649         bool fIsVertical;
1650 
isNameDisplayed()1651         bool isNameDisplayed()
1652         {
1653             return (!(getName().startsWith("0x")) && getName().isNotEmpty());
1654         }
1655 
1656         /**
1657          * \brief   Return the vertical dimension size for a child to be displayed in.
1658          *
1659          */
getVSpaceToRemove()1660         int getVSpaceToRemove()
1661         {
1662             // Checking if the name is displayed, to give to good amount space for child components
1663             // kNameHeight pixels is the bix name, kMargin pixel per child components for the margins
1664             if (isNameDisplayed()) {
1665                 return (getHeight() - kNameHeight - kMargin * getNumChildComponents());
1666             } else {
1667                 return (getHeight() - kMargin * getNumChildComponents());
1668             }
1669         }
1670 
1671         /**
1672          * \brief   Return the vertical dimension size for a child to be displayed in.
1673          *
1674          */
getHSpaceToRemove()1675         int getHSpaceToRemove()
1676         {
1677             // Don't need to check for an horizontal box, as it height doesn't matter
1678             return (getWidth() - kMargin * getNumChildComponents());
1679         }
1680 
1681     public:
1682         /**
1683          * \brief   Constructor.
1684          * \details Initialize uiBase variables and uiBox specific ones.
1685          *
1686          * \param   vert        True if it's a vertical box, false otherwise.
1687          * \param   boxName     Name of the uiBox.
1688          */
uiBox(bool vert,juce::String boxName)1689         uiBox(bool vert, juce::String boxName): uiBase(0,0), juce::Component(boxName), fIsVertical(vert)
1690         {}
1691 
1692         /**
1693          * \brief   Destructor.
1694          * \details Delete all uiBox recusively, but not the uiComponent,
1695          *          because it's handled by the uiItem FAUST objects.
1696          */
~uiBox()1697         virtual ~uiBox()
1698         {
1699             /*
1700              Deleting boxes, from leaves to root:
1701              - leaves (uiComponent) are deleted by the uiItem mechanism
1702              - containers (uiBox and uiTabBox) have to be explicitly deleted
1703              */
1704             for (int i = getNumChildComponents()-1; i >= 0; i--) {
1705                 delete dynamic_cast<uiBox*>(getChildComponent(i));
1706                 delete dynamic_cast<uiTabBox*>(getChildComponent(i));
1707             }
1708         }
1709 
1710         /**
1711          * \brief   Initialization of the DisplayRect and Total size.
1712          * \details Calculate the correct size for each box, depending on its child sizes.
1713          */
setRecommendedSize()1714         void setRecommendedSize() override
1715         {
1716             // Initialized each time
1717             fDisplayRectWidth = fDisplayRectHeight = 0;
1718 
1719             // Display rectangle size is the sum of a dimension on a side, and the max of the other one
1720             // on the other side, depending on its orientation (horizontal/vertical).
1721             // Using child's totalSize, because the display rectangle size need to be as big as
1722             // all of its child components with their margins included.
1723             for (int j = 0; j < getNumChildComponents(); j++) {
1724                 uiBase* base_comp = dynamic_cast<uiBase*>(getChildComponent(j));
1725                 if (fIsVertical) {
1726                     fDisplayRectWidth = juce::jmax(fDisplayRectWidth, base_comp->getTotalWidth());
1727                     fDisplayRectHeight += base_comp->getTotalHeight();
1728                 } else {
1729                     fDisplayRectWidth += base_comp->getTotalWidth();
1730                     fDisplayRectHeight = juce::jmax(fDisplayRectHeight, base_comp->getTotalHeight());
1731                 }
1732             }
1733 
1734             fTotalHeight = fDisplayRectHeight;
1735             fTotalWidth = fDisplayRectWidth;
1736 
1737             // Adding kMargin pixels of margins per child component on a dimension, and just kMargin on
1738             // the other one, depending on its orientation
1739 
1740             if (fIsVertical) {
1741                 fTotalHeight += kMargin * getNumChildComponents();
1742                 fTotalWidth += kMargin;
1743             } else {
1744                 fTotalWidth += kMargin * getNumChildComponents();
1745                 fTotalHeight += kMargin;
1746             }
1747 
1748             // Adding kNameHeight pixels on its height to allow the name to be displayed
1749             if (isNameDisplayed()) {
1750                 fTotalHeight += kNameHeight;
1751             }
1752         }
1753 
1754         /** Initiate the current box ratio, and its child's ones recursively. */
1755         void init(juce::Component* comp = nullptr) override
1756         {
1757             uiBase::init(this);
1758 
1759             // Going through the Component tree recursively
1760             for (int i = 0; i < getNumChildComponents(); i++) {
1761                 Component* comp = getChildComponent(i);
1762                 uiBase* base_comp = dynamic_cast<uiBase*>(comp);
1763                 base_comp->init(comp);
1764             }
1765         }
1766 
1767         /**
1768          * \brief   Main layout function.
1769          * \details Allow to place all uiBase child correctly according to their ratios
1770          *          and the current box size.
1771          *
1772          * \param   displayRect    Absolute raw bounds of the current box (with margins
1773          *                          and space for the title).
1774          */
resized()1775         void resized() override
1776         {
1777             juce::Rectangle<int> displayRect = getBounds();
1778 
1779             // Deleting space for the box name if it needs to be shown
1780             if (isNameDisplayed()) {
1781                 displayRect.removeFromTop(kNameHeight);
1782             }
1783 
1784             // Putting the margins
1785             displayRect.reduce(kMargin/2, kMargin/2);
1786 
1787             // Give child components an adapt size depending on its ratio and the current box size
1788             for (int i = 0; i < getNumChildComponents(); i++) {
1789                 juce::Component* comp = getChildComponent(i);
1790                 uiBase* base_comp = dynamic_cast<uiBase*>(comp);
1791 
1792                 if (fIsVertical) {
1793                     int heightToRemove = getVSpaceToRemove() * base_comp->getVRatio();
1794                     // Remove the space needed from the displayRect, and translate it to show the margins
1795                     base_comp->setRelativeSize(comp, displayRect.removeFromTop(heightToRemove).translated(0, kMargin * i));
1796                 } else {
1797                     int widthToRemove = getHSpaceToRemove() * base_comp->getHRatio();
1798                     // Remove the space needed from the displayRect, and translate it to show the margins
1799                     base_comp->setRelativeSize(comp, displayRect.removeFromLeft(widthToRemove).translated(kMargin * i, 0));
1800                 }
1801             }
1802         }
1803 
1804         /**
1805          * Fill the uiBox bounds with a grey color, different shades depending on its order.
1806          * Write the uiBox name if it needs to.
1807          */
paint(juce::Graphics & g)1808         void paint(juce::Graphics& g) override
1809         {
1810             // Fill the box background in gray shades
1811             g.setColour(juce::Colours::black.withAlpha(0.05f));
1812             g.fillRect(getLocalBounds());
1813 
1814             // Display the name if it's needed
1815             if (isNameDisplayed()) {
1816                 g.setColour(juce::Colours::black);
1817                 g.drawText(getName(), getLocalBounds().withHeight(kNameHeight), juce::Justification::centred);
1818             }
1819         }
1820 
add(juce::Component * comp)1821         void add(juce::Component* comp) override
1822         {
1823             addAndMakeVisible(comp);
1824         }
1825 
1826 };
1827 
1828 /** Class in charge of doing the glue between FAUST and JUCE */
1829 class JuceGUI : public GUI, public MetaDataUI, public juce::Component
1830 {
1831 
1832     private:
1833 
1834         bool fDefault = true;
1835         std::stack<uiBase*> fBoxStack;
1836         uiBase* fCurrentBox = nullptr;   // Current box used in buildUserInterface logic.
1837 
1838         int fRadioGroupID;               // In case of radio buttons.
1839         std::unique_ptr<juce::LookAndFeel> fLaf = std::make_unique<juce::LookAndFeel_V4>();
1840 
defaultVal(FAUSTFLOAT * zone,FAUSTFLOAT def)1841         FAUSTFLOAT defaultVal(FAUSTFLOAT* zone, FAUSTFLOAT def)
1842         {
1843             return (fDefault) ? def : *zone;
1844         }
1845 
1846         /** Add generic box to the user interface. */
openBox(uiBase * box)1847         void openBox(uiBase* box)
1848         {
1849             if (fCurrentBox) {
1850                 fCurrentBox->add(dynamic_cast<juce::Component*>(box));
1851                 fBoxStack.push(fCurrentBox);
1852             }
1853             fCurrentBox = box;
1854         }
1855 
1856         /** Add a slider to the user interface. */
addSlider(const char * label,FAUSTFLOAT * zone,FAUSTFLOAT init,FAUSTFLOAT min,FAUSTFLOAT max,FAUSTFLOAT step,int kWidth,int kHeight,SliderType type)1857         void addSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step, int kWidth, int kHeight, SliderType type)
1858         {
1859             if (isKnob(zone)) {
1860                 addKnob(label, zone, defaultVal(zone, init), min, max, step);
1861             } else if (isRadio(zone)) {
1862                 addRadioButtons(label, zone, defaultVal(zone, init), min, max, step, fRadioDescription[zone].c_str(), false);
1863             } else if (isMenu(zone)) {
1864                 addMenu(label, zone, defaultVal(zone, init), min, max, step, fMenuDescription[zone].c_str());
1865             } else {
1866                 fCurrentBox->add(new uiSlider(this, zone, kWidth, kHeight, defaultVal(zone, init), min, max, step, juce::String(label), juce::String(fUnit[zone]), juce::String(fTooltip[zone]), getScale(zone), type));
1867             }
1868         }
1869 
1870         /** Add a radio buttons to the user interface. */
addRadioButtons(const char * label,FAUSTFLOAT * zone,FAUSTFLOAT init,FAUSTFLOAT min,FAUSTFLOAT max,FAUSTFLOAT step,const char * mdescr,bool vert)1871         void addRadioButtons(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step, const char* mdescr, bool vert)
1872         {
1873             std::vector<std::string> names;
1874             std::vector<double> values;
1875             parseMenuList(mdescr, names, values); // Set names and values vectors
1876 
1877             // and not just n checkButtons :
1878             // TODO : check currently unused checkButtonWidth...
1879             int checkButtonWidth = 0;
1880             for (int i = 0; i < names.size(); i++) {
1881                 // Checking the maximum of horizontal space needed to display the radio buttons
1882                 checkButtonWidth = juce::jmax(juce::Font().getStringWidth(juce::String(names[i])) + 15, checkButtonWidth);
1883             }
1884 
1885             if (vert) {
1886                 fCurrentBox->add(new uiRadioButton(this, zone, juce::String(label), kCheckButtonWidth, names.size() * (kRadioButtonHeight - 25) + 25, defaultVal(zone, init), min, max, true, names, values, juce::String(fTooltip[zone]), fRadioGroupID++));
1887             } else {
1888                 fCurrentBox->add(new uiRadioButton(this, zone, juce::String(label), kCheckButtonWidth, kRadioButtonHeight, defaultVal(zone, init), min, max, false, names, values, juce::String(fTooltip[zone]), fRadioGroupID++));
1889             }
1890         }
1891 
1892         /** Add a menu to the user interface. */
addMenu(const char * label,FAUSTFLOAT * zone,FAUSTFLOAT init,FAUSTFLOAT min,FAUSTFLOAT max,FAUSTFLOAT step,const char * mdescr)1893         void addMenu(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step, const char* mdescr)
1894         {
1895             fCurrentBox->add(new uiMenu(this, zone, juce::String(label), kMenuWidth, kMenuHeight, defaultVal(zone, init), min, max, juce::String(fTooltip[zone]), mdescr));
1896         }
1897 
1898         /** Add a ciruclar slider to the user interface. */
addKnob(const char * label,FAUSTFLOAT * zone,FAUSTFLOAT init,FAUSTFLOAT min,FAUSTFLOAT max,FAUSTFLOAT step)1899         void addKnob(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) {
1900             fCurrentBox->add(new uiSlider(this, zone, kKnobWidth, kKnobHeight, defaultVal(zone, init), min, max, step, juce::String(label), juce::String(fUnit[zone]), juce::String(fTooltip[zone]), getScale(zone), Knob));
1901         }
1902 
1903         /** Add a bargraph to the user interface. */
addBargraph(const char * label,FAUSTFLOAT * zone,FAUSTFLOAT min,FAUSTFLOAT max,int kWidth,int kHeight,VUMeterType type)1904         void addBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max, int kWidth, int kHeight, VUMeterType type)
1905         {
1906             if (isLed(zone)) {
1907                 addLed(juce::String(label), zone, min, max);
1908             } else if (isNumerical(zone)) {
1909                 addNumericalDisplay(juce::String(label), zone, min, max);
1910             } else {
1911                 fCurrentBox->add(new uiVUMeter (this, zone, kWidth, kHeight, juce::String(label), min, max, juce::String(fUnit[zone]), juce::String(fTooltip[zone]), type, false));
1912             }
1913         }
1914 
1915     public:
1916         /**
1917          * \brief   Constructor.
1918          * \details Initialize the JuceGUI specific variables.
1919          */
fDefault(def)1920         JuceGUI(bool def = true):fDefault(def), fRadioGroupID(1) // fRadioGroupID must start at 1
1921         {
1922             setLookAndFeel(fLaf.get());
1923         }
1924 
1925         /**
1926          * \brief   Destructor.
1927          * \details Delete root box used in buildUserInterface logic.
1928          */
~JuceGUI()1929         virtual ~JuceGUI()
1930         {
1931             setLookAndFeel(nullptr);
1932             delete fCurrentBox;
1933         }
1934 
1935         /** Return the size of the FAUST program */
getSize()1936         juce::Rectangle<int> getSize()
1937         {
1938             // Mininum size in case of empty GUI
1939             if (fCurrentBox) {
1940                 juce::Rectangle<int> res = fCurrentBox->getSize();
1941                 res.setSize(std::max<int>(1, res.getWidth()), std::max<int>(1, res.getHeight()));
1942                 return res;
1943             } else {
1944                 return juce::Rectangle<int>(0, 0, 1, 1);
1945             }
1946         }
1947 
1948         /** Initialize the uiTabBox component to be visible. */
openTabBox(const char * label)1949         virtual void openTabBox(const char* label) override
1950         {
1951             openBox(new uiTabBox());
1952         }
1953 
1954         /** Add a new vertical box to the user interface. */
openVerticalBox(const char * label)1955         virtual void openVerticalBox(const char* label) override
1956         {
1957             openBox(new uiBox(true, juce::String(label)));
1958         }
1959 
1960         /** Add a new horizontal box to the user interface. */
openHorizontalBox(const char * label)1961         virtual void openHorizontalBox(const char* label) override
1962         {
1963             openBox(new uiBox(false, juce::String(label)));
1964         }
1965 
1966         /** Close the current box. */
closeBox()1967         virtual void closeBox() override
1968         {
1969             fCurrentBox->setRecommendedSize();
1970 
1971             if (fBoxStack.empty()) {
1972                 // Add root box in JuceGUI component
1973                 addAndMakeVisible(dynamic_cast<juce::Component*>(fCurrentBox));
1974                 fCurrentBox->init();
1975                 // Force correct draw
1976                 resized();
1977             } else {
1978                 fCurrentBox = fBoxStack.top();
1979                 fBoxStack.pop();
1980             }
1981         }
1982 
1983         /** Add an horizontal slider to the user interface. */
addHorizontalSlider(const char * label,FAUSTFLOAT * zone,FAUSTFLOAT init,FAUSTFLOAT min,FAUSTFLOAT max,FAUSTFLOAT step)1984         virtual void addHorizontalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) override
1985         {
1986             addSlider(label, zone, init, min, max, step, kHSliderWidth, kHSliderHeight, HSlider);
1987         }
1988 
1989         /** Add a vertical slider to the user interface. */
addVerticalSlider(const char * label,FAUSTFLOAT * zone,FAUSTFLOAT init,FAUSTFLOAT min,FAUSTFLOAT max,FAUSTFLOAT step)1990         virtual void addVerticalSlider(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) override
1991         {
1992             int newWidth = juce::jmax(juce::Font().getStringWidth(juce::String(label)), kVSliderWidth) + kMargin;
1993             addSlider(label, zone, init, min, max, step, newWidth, kVSliderHeight, VSlider);
1994         }
1995 
1996         /** Add a button to the user interface. */
addButton(const char * label,FAUSTFLOAT * zone)1997         virtual void addButton(const char* label, FAUSTFLOAT* zone) override
1998         {
1999             fCurrentBox->add(new uiButton(this, zone, kButtonWidth, kButtonHeight, juce::String(label), juce::String(fTooltip[zone])));
2000         }
2001 
2002         /** Add a check button to the user interface. */
addCheckButton(const char * label,FAUSTFLOAT * zone)2003         virtual void addCheckButton(const char* label, FAUSTFLOAT* zone) override
2004         {
2005             // newWidth is his text size, plus the check box size
2006             int newWidth = juce::Font().getStringWidth(juce::String(label)) + kCheckButtonWidth;
2007             fCurrentBox->add(new uiCheckButton(this, zone, newWidth, kCheckButtonHeight, juce::String(label), juce::String(fTooltip[zone])));
2008         }
2009 
2010         /** Add a numerical entry to the user interface. */
addNumEntry(const char * label,FAUSTFLOAT * zone,FAUSTFLOAT init,FAUSTFLOAT min,FAUSTFLOAT max,FAUSTFLOAT step)2011         virtual void addNumEntry(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT init, FAUSTFLOAT min, FAUSTFLOAT max, FAUSTFLOAT step) override
2012         {
2013             // kMargin pixels between the slider and his name
2014             int newWidth = juce::Font().getStringWidth(juce::String(label)) + kNumEntryWidth + kMargin;
2015             fCurrentBox->add(new uiSlider(this, zone, newWidth, kNumEntryHeight, defaultVal(zone, init), min, max, step, juce::String(label), juce::String(fUnit[zone]), juce::String(fTooltip[zone]), getScale(zone), NumEntry));
2016         }
2017 
2018         /** Add a vertical bargraph to the user interface. */
addVerticalBargraph(const char * label,FAUSTFLOAT * zone,FAUSTFLOAT min,FAUSTFLOAT max)2019         virtual void addVerticalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) override
2020         {
2021             addBargraph(label, zone, min, max, kVBargraphWidth, kVBargraphHeight, VVUMeter);
2022         }
2023 
2024         /** Add a vertical bargraph to the user interface. */
addHorizontalBargraph(const char * label,FAUSTFLOAT * zone,FAUSTFLOAT min,FAUSTFLOAT max)2025         virtual void addHorizontalBargraph(const char* label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) override
2026         {
2027             addBargraph(label, zone, min, max, kHBargraphWidth, kHBargraphHeight, HVUMeter);
2028         }
2029 
2030         /** Add a LED to the user interface. */
addLed(juce::String label,FAUSTFLOAT * zone,FAUSTFLOAT min,FAUSTFLOAT max)2031         void addLed(juce::String label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max)
2032         {
2033             fCurrentBox->add(new uiVUMeter(this, zone, kLedWidth, kLedHeight, label, min, max, juce::String(fUnit[zone]), juce::String(fTooltip[zone]), Led, false));
2034         }
2035 
2036         /** Add a numerical display to the user interface. */
addNumericalDisplay(juce::String label,FAUSTFLOAT * zone,FAUSTFLOAT min,FAUSTFLOAT max)2037         void addNumericalDisplay(juce::String label, FAUSTFLOAT* zone, FAUSTFLOAT min, FAUSTFLOAT max) {
2038             fCurrentBox->add(new uiVUMeter(this, zone, kNumDisplayWidth, kNumDisplayHeight, label, min, max, juce::String(fUnit[zone]), juce::String(fTooltip[zone]), NumDisplay, false));
2039         }
2040 
2041         /** Declare a metadata. */
declare(FAUSTFLOAT * zone,const char * key,const char * value)2042         virtual void declare(FAUSTFLOAT* zone, const char* key, const char* value) override
2043         {
2044             MetaDataUI::declare(zone, key, value);
2045         }
2046 
2047         /** Resize its child to match the new bounds */
resized()2048         void resized() override
2049         {
2050             if (fCurrentBox) {
2051                 dynamic_cast<Component*>(fCurrentBox)->setBounds(getLocalBounds());
2052             }
2053         }
2054 
2055 };
2056 
2057 #endif
2058 /**************************  END  JuceGUI.h **************************/
2059