1 /* 2 ============================================================================== 3 4 This file is part of the JUCE library. 5 Copyright (c) 2020 - Raw Material Software Limited 6 7 JUCE is an open source library subject to commercial or open-source 8 licensing. 9 10 By using JUCE, you agree to the terms of both the JUCE 6 End-User License 11 Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). 12 13 End User License Agreement: www.juce.com/juce-6-licence 14 Privacy Policy: www.juce.com/juce-privacy-policy 15 16 Or: You may also use this code under the terms of the GPL v3 (see 17 www.gnu.org/licenses). 18 19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER 20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE 21 DISCLAIMED. 22 23 ============================================================================== 24 */ 25 26 #pragma once 27 28 29 //============================================================================== 30 struct ContentViewHeader : public Component 31 { ContentViewHeaderContentViewHeader32 ContentViewHeader (String headerName, Icon headerIcon) 33 : name (headerName), icon (headerIcon) 34 { 35 } 36 paintContentViewHeader37 void paint (Graphics& g) override 38 { 39 g.fillAll (findColour (contentHeaderBackgroundColourId)); 40 41 auto bounds = getLocalBounds().reduced (20, 0); 42 43 icon.withColour (Colours::white).draw (g, bounds.toFloat().removeFromRight (30), false); 44 45 g.setColour (Colours::white); 46 g.setFont (Font (18.0f)); 47 g.drawFittedText (name, bounds, Justification::centredLeft, 1); 48 } 49 50 String name; 51 Icon icon; 52 }; 53 54 //============================================================================== 55 class ListBoxHeader : public Component 56 { 57 public: ListBoxHeader(Array<String> columnHeaders)58 ListBoxHeader (Array<String> columnHeaders) 59 { 60 for (auto s : columnHeaders) 61 { 62 addAndMakeVisible (headers.add (new Label (s, s))); 63 widths.add (1.0f / (float) columnHeaders.size()); 64 } 65 66 setSize (200, 40); 67 } 68 ListBoxHeader(Array<String> columnHeaders,Array<float> columnWidths)69 ListBoxHeader (Array<String> columnHeaders, Array<float> columnWidths) 70 { 71 jassert (columnHeaders.size() == columnWidths.size()); 72 73 auto index = 0; 74 for (auto s : columnHeaders) 75 { 76 addAndMakeVisible (headers.add (new Label (s, s))); 77 widths.add (columnWidths.getUnchecked (index++)); 78 } 79 80 recalculateWidths(); 81 82 setSize (200, 40); 83 } 84 resized()85 void resized() override 86 { 87 auto bounds = getLocalBounds(); 88 auto width = bounds.getWidth(); 89 90 auto index = 0; 91 for (auto h : headers) 92 { 93 auto headerWidth = roundToInt ((float) width * widths.getUnchecked (index)); 94 h->setBounds (bounds.removeFromLeft (headerWidth)); 95 ++index; 96 } 97 } 98 setColumnHeaderWidth(int index,float proportionOfWidth)99 void setColumnHeaderWidth (int index, float proportionOfWidth) 100 { 101 if (! (isPositiveAndBelow (index, headers.size()) && isPositiveAndNotGreaterThan (proportionOfWidth, 1.0f))) 102 { 103 jassertfalse; 104 return; 105 } 106 107 widths.set (index, proportionOfWidth); 108 recalculateWidths (index); 109 } 110 getColumnX(int index)111 int getColumnX (int index) 112 { 113 auto prop = 0.0f; 114 for (int i = 0; i < index; ++i) 115 prop += widths.getUnchecked (i); 116 117 return roundToInt (prop * (float) getWidth()); 118 } 119 getProportionAtIndex(int index)120 float getProportionAtIndex (int index) 121 { 122 jassert (isPositiveAndBelow (index, widths.size())); 123 return widths.getUnchecked (index); 124 } 125 126 private: 127 OwnedArray<Label> headers; 128 Array<float> widths; 129 130 void recalculateWidths (int indexToIgnore = -1) 131 { 132 auto total = 0.0f; 133 134 for (auto w : widths) 135 total += w; 136 137 if (total == 1.0f) 138 return; 139 140 auto diff = 1.0f - total; 141 auto amount = diff / static_cast<float> (indexToIgnore == -1 ? widths.size() : widths.size() - 1); 142 143 for (int i = 0; i < widths.size(); ++i) 144 { 145 if (i != indexToIgnore) 146 { 147 auto val = widths.getUnchecked (i); 148 widths.set (i, val + amount); 149 } 150 } 151 } 152 }; 153 154 //============================================================================== 155 class InfoButton : public Button 156 { 157 public: 158 InfoButton (const String& infoToDisplay = {}) 159 : Button ({}) 160 { 161 if (infoToDisplay.isNotEmpty()) 162 setInfoToDisplay (infoToDisplay); 163 } 164 paintButton(Graphics & g,bool isMouseOverButton,bool isButtonDown)165 void paintButton (Graphics& g, bool isMouseOverButton, bool isButtonDown) override 166 { 167 auto bounds = getLocalBounds().toFloat().reduced (2); 168 auto& icon = getIcons().info; 169 170 g.setColour (findColour (treeIconColourId).withMultipliedAlpha (isMouseOverButton || isButtonDown ? 1.0f : 0.5f)); 171 172 if (isButtonDown) 173 g.fillEllipse (bounds); 174 else 175 g.fillPath (icon, RectanglePlacement (RectanglePlacement::centred) 176 .getTransformToFit (icon.getBounds(), bounds)); 177 } 178 clicked()179 void clicked() override 180 { 181 auto w = std::make_unique<InfoWindow> (info); 182 w->setSize (width, w->getHeight() * numLines + 10); 183 184 CallOutBox::launchAsynchronously (std::move (w), getScreenBounds(), nullptr); 185 } 186 187 using Button::clicked; 188 setInfoToDisplay(const String & infoToDisplay)189 void setInfoToDisplay (const String& infoToDisplay) 190 { 191 if (infoToDisplay.isNotEmpty()) 192 { 193 info = infoToDisplay; 194 195 auto stringWidth = roundToInt (Font (14.0f).getStringWidthFloat (info)); 196 width = jmin (300, stringWidth); 197 198 numLines += static_cast<int> (stringWidth / width); 199 } 200 } 201 setAssociatedComponent(Component * comp)202 void setAssociatedComponent (Component* comp) { associatedComponent = comp; } getAssociatedComponent()203 Component* getAssociatedComponent() { return associatedComponent; } 204 205 private: 206 String info; 207 Component* associatedComponent = nullptr; 208 int width; 209 int numLines = 1; 210 211 //============================================================================== 212 struct InfoWindow : public Component 213 { InfoWindowInfoWindow214 InfoWindow (const String& s) 215 : stringToDisplay (s) 216 { 217 setSize (150, 14); 218 } 219 paintInfoWindow220 void paint (Graphics& g) override 221 { 222 g.fillAll (findColour (secondaryBackgroundColourId)); 223 224 g.setColour (findColour (defaultTextColourId)); 225 g.setFont (Font (14.0f)); 226 g.drawFittedText (stringToDisplay, getLocalBounds(), Justification::centred, 15, 0.75f); 227 } 228 229 String stringToDisplay; 230 }; 231 232 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InfoButton) 233 }; 234 235 //============================================================================== 236 class PropertyGroupComponent : public Component, 237 private TextPropertyComponent::Listener 238 { 239 public: 240 PropertyGroupComponent (String name, Icon icon, String desc = {}) header(name,icon)241 : header (name, icon), 242 description (desc) 243 { 244 addAndMakeVisible (header); 245 246 description.setFont ({ 16.0f }); 247 description.setColour (getLookAndFeel().findColour (defaultTextColourId)); 248 description.setLineSpacing (5.0f); 249 description.setJustification (Justification::centredLeft); 250 } 251 setProperties(const PropertyListBuilder & newProps)252 void setProperties (const PropertyListBuilder& newProps) 253 { 254 infoButtons.clear(); 255 properties.clear(); 256 properties.addArray (newProps.components); 257 258 for (auto* prop : properties) 259 { 260 addAndMakeVisible (prop); 261 262 if (! prop->getTooltip().isEmpty()) 263 { 264 addAndMakeVisible (infoButtons.add (new InfoButton (prop->getTooltip()))); 265 infoButtons.getLast()->setAssociatedComponent (prop); 266 prop->setTooltip ({}); // set the tooltip to empty so it only displays when its button is clicked 267 } 268 269 if (auto* multiChoice = dynamic_cast<MultiChoicePropertyComponent*> (prop)) 270 multiChoice->onHeightChange = [this] { updateSize(); }; 271 272 if (auto* text = dynamic_cast<TextPropertyComponent*> (prop)) 273 if (text->isTextEditorMultiLine()) 274 text->addListener (this); 275 276 } 277 } 278 updateSize(int x,int y,int width)279 int updateSize (int x, int y, int width) 280 { 281 header.setBounds (0, 0, width, headerSize); 282 auto height = header.getBottom() + 10; 283 284 descriptionLayout.createLayout (description, (float) (width - 40)); 285 auto descriptionHeight = (int) descriptionLayout.getHeight(); 286 287 if (descriptionHeight > 0) 288 height += (int) descriptionLayout.getHeight() + 25; 289 290 for (auto* pp : properties) 291 { 292 auto propertyHeight = pp->getPreferredHeight() + (getHeightMultiplier (pp) * pp->getPreferredHeight()); 293 294 InfoButton* buttonToUse = nullptr; 295 for (auto* b : infoButtons) 296 if (b->getAssociatedComponent() == pp) 297 buttonToUse = b; 298 299 if (buttonToUse != nullptr) 300 { 301 buttonToUse->setSize (20, 20); 302 buttonToUse->setCentrePosition (20, height + (propertyHeight / 2)); 303 } 304 305 pp->setBounds (40, height, width - 50, propertyHeight); 306 307 if (shouldResizePropertyComponent (pp)) 308 resizePropertyComponent (pp); 309 310 height += pp->getHeight() + 10; 311 } 312 313 height += 16; 314 315 setBounds (x, y, width, jmax (height, getParentHeight())); 316 317 return height; 318 } 319 paint(Graphics & g)320 void paint (Graphics& g) override 321 { 322 g.setColour (findColour (secondaryBackgroundColourId)); 323 g.fillRect (getLocalBounds()); 324 325 auto textArea = getLocalBounds().toFloat() 326 .withTop ((float) headerSize) 327 .reduced (20.0f, 10.0f) 328 .withHeight (descriptionLayout.getHeight()); 329 descriptionLayout.draw (g, textArea); 330 } 331 332 OwnedArray<PropertyComponent> properties; 333 334 private: 335 //============================================================================== textPropertyComponentChanged(TextPropertyComponent * comp)336 void textPropertyComponentChanged (TextPropertyComponent* comp) override 337 { 338 auto fontHeight = [comp] 339 { 340 Label tmpLabel; 341 return comp->getLookAndFeel().getLabelFont (tmpLabel).getHeight(); 342 }(); 343 344 auto lines = StringArray::fromLines (comp->getText()); 345 346 comp->setPreferredHeight (jmax (100, 10 + roundToInt (fontHeight * (float) lines.size()))); 347 348 updateSize(); 349 } 350 351 //============================================================================== updateSize()352 void updateSize() 353 { 354 updateSize (getX(), getY(), getWidth()); 355 356 if (auto* parent = getParentComponent()) 357 parent->parentSizeChanged(); 358 } 359 360 //============================================================================== shouldResizePropertyComponent(PropertyComponent * p)361 bool shouldResizePropertyComponent (PropertyComponent* p) 362 { 363 if (auto* textComp = dynamic_cast<TextPropertyComponent*> (p)) 364 return ! textComp->isTextEditorMultiLine(); 365 366 return (dynamic_cast<ChoicePropertyComponent*> (p) != nullptr 367 || dynamic_cast<ButtonPropertyComponent*> (p) != nullptr 368 || dynamic_cast<BooleanPropertyComponent*> (p) != nullptr); 369 } 370 resizePropertyComponent(PropertyComponent * pp)371 void resizePropertyComponent (PropertyComponent* pp) 372 { 373 for (auto i = pp->getNumChildComponents() - 1; i >= 0; --i) 374 { 375 auto* child = pp->getChildComponent (i); 376 377 auto bounds = child->getBounds(); 378 child->setBounds (bounds.withSizeKeepingCentre (child->getWidth(), pp->getPreferredHeight())); 379 } 380 } 381 getHeightMultiplier(PropertyComponent * pp)382 int getHeightMultiplier (PropertyComponent* pp) 383 { 384 auto availableTextWidth = ProjucerLookAndFeel::getTextWidthForPropertyComponent (pp); 385 386 auto font = ProjucerLookAndFeel::getPropertyComponentFont(); 387 auto nameWidth = font.getStringWidthFloat (pp->getName()); 388 389 if (availableTextWidth == 0) 390 return 0; 391 392 return static_cast<int> (nameWidth / (float) availableTextWidth); 393 } 394 395 OwnedArray<InfoButton> infoButtons; 396 ContentViewHeader header; 397 AttributedString description; 398 TextLayout descriptionLayout; 399 int headerSize = 40; 400 401 //============================================================================== 402 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertyGroupComponent) 403 }; 404