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