| @@ -141,58 +141,27 @@ static Array<PropertyComponent*> createChoices (int howMany) | |||
| StringArray choices; | |||
| Array<var> choiceVars; | |||
| for (int i = 0; i < howMany; ++i) | |||
| for (int i = 0; i < 12; ++i) | |||
| { | |||
| choices.add ("Item " + String (i)); | |||
| choiceVars.add (i); | |||
| } | |||
| for (int i = 0; i < howMany; ++i) | |||
| comps.add (new ChoicePropertyComponent (Value (Random::getSystemRandom().nextInt (6)), "Choice Property " + String (i + 1), choices, choiceVars)); | |||
| comps.add (new ChoicePropertyComponent (Value (Random::getSystemRandom().nextInt (12)), "Choice Property " + String (i + 1), choices, choiceVars)); | |||
| for (int i = 0; i < howMany; ++i) | |||
| comps.add (new MultiChoicePropertyComponent (Value (Array<var>()), "Multi-Choice Property " + String (i + 1), choices, choiceVars)); | |||
| return comps; | |||
| } | |||
| //============================================================================== | |||
| class PropertiesDemo : public Component | |||
| { | |||
| public: | |||
| PropertiesDemo() | |||
| { | |||
| setOpaque (true); | |||
| addAndMakeVisible (propertyPanel); | |||
| propertyPanel.addSection ("Text Editors", createTextEditors()); | |||
| propertyPanel.addSection ("Sliders", createSliders (3)); | |||
| propertyPanel.addSection ("Choice Properties", createChoices (6)); | |||
| propertyPanel.addSection ("Buttons & Toggles", createButtons (3)); | |||
| setSize (750, 650); | |||
| } | |||
| void paint (Graphics& g) override | |||
| { | |||
| g.fillAll (getUIColourIfAvailable (LookAndFeel_V4::ColourScheme::UIColour::windowBackground, | |||
| Colour::greyLevel (0.8f))); | |||
| } | |||
| void resized() override | |||
| { | |||
| propertyPanel.setBounds (getLocalBounds().reduced (4)); | |||
| } | |||
| private: | |||
| PropertyPanel propertyPanel; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertiesDemo) | |||
| }; | |||
| //============================================================================== | |||
| class ConcertinaDemo : public Component, | |||
| class PropertiesDemo : public Component, | |||
| private Timer | |||
| { | |||
| public: | |||
| ConcertinaDemo() | |||
| PropertiesDemo() | |||
| { | |||
| setOpaque (true); | |||
| addAndMakeVisible (concertinaPanel); | |||
| @@ -212,7 +181,7 @@ public: | |||
| { | |||
| auto* panel = new PropertyPanel ("Choice Properties"); | |||
| panel->addProperties (createChoices (12)); | |||
| panel->addProperties (createChoices (3)); | |||
| addPanel (panel); | |||
| } | |||
| @@ -222,6 +191,8 @@ public: | |||
| addPanel (panel); | |||
| } | |||
| setSize (750, 650); | |||
| startTimer (300); | |||
| } | |||
| @@ -251,5 +222,5 @@ private: | |||
| concertinaPanel.setMaximumPanelSize (panel, panel->getTotalContentHeight()); | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConcertinaDemo) | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertiesDemo) | |||
| }; | |||
| @@ -235,6 +235,7 @@ namespace juce | |||
| #include "properties/juce_PropertyPanel.cpp" | |||
| #include "properties/juce_SliderPropertyComponent.cpp" | |||
| #include "properties/juce_TextPropertyComponent.cpp" | |||
| #include "properties/juce_MultiChoicePropertyComponent.cpp" | |||
| #include "widgets/juce_ComboBox.cpp" | |||
| #include "widgets/juce_ImageComponent.cpp" | |||
| #include "widgets/juce_Label.cpp" | |||
| @@ -280,6 +280,7 @@ namespace juce | |||
| #include "properties/juce_PropertyPanel.h" | |||
| #include "properties/juce_SliderPropertyComponent.h" | |||
| #include "properties/juce_TextPropertyComponent.h" | |||
| #include "properties/juce_MultiChoicePropertyComponent.h" | |||
| #include "application/juce_Application.h" | |||
| #include "misc/juce_BubbleComponent.h" | |||
| #include "lookandfeel/juce_LookAndFeel.h" | |||
| @@ -0,0 +1,244 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| class MultiChoicePropertyComponent::MultiChoiceRemapperSource : public Value::ValueSource, | |||
| private Value::Listener | |||
| { | |||
| public: | |||
| MultiChoiceRemapperSource (const Value& source, var v) | |||
| : sourceValue (source), | |||
| varToControl (v) | |||
| { | |||
| sourceValue.addListener (this); | |||
| } | |||
| var getValue() const override | |||
| { | |||
| if (auto* arr = sourceValue.getValue().getArray()) | |||
| if (arr->contains (varToControl)) | |||
| return 1; | |||
| return 0; | |||
| } | |||
| void setValue (const var& newValue) override | |||
| { | |||
| if (auto* arr = sourceValue.getValue().getArray()) | |||
| { | |||
| auto newValueInt = static_cast<int> (newValue); | |||
| if (newValueInt == 1) | |||
| arr->addIfNotAlreadyThere (varToControl); | |||
| else | |||
| arr->remove (arr->indexOf (varToControl)); | |||
| } | |||
| } | |||
| private: | |||
| Value sourceValue; | |||
| var varToControl; | |||
| void valueChanged (Value&) override { sendChangeMessage (true); } | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiChoiceRemapperSource) | |||
| }; | |||
| //============================================================================== | |||
| class MultiChoicePropertyComponent::MultiChoiceRemapperSourceWithDefault : public Value::ValueSource, | |||
| private Value::Listener | |||
| { | |||
| public: | |||
| MultiChoiceRemapperSourceWithDefault (const ValueWithDefault& vwd, var v) | |||
| : valueWithDefault (vwd), | |||
| sourceValue (valueWithDefault.getPropertyAsValue()), | |||
| varToControl (v) | |||
| { | |||
| sourceValue.addListener (this); | |||
| } | |||
| var getValue() const override | |||
| { | |||
| if (auto* arr = valueWithDefault.get().getArray()) | |||
| if (arr->contains (varToControl)) | |||
| return 1; | |||
| return 0; | |||
| } | |||
| void setValue (const var& newValue) override | |||
| { | |||
| if (auto* arr = valueWithDefault.get().getArray()) | |||
| { | |||
| auto newValueInt = static_cast<int> (newValue); | |||
| if (newValueInt == 1) | |||
| arr->addIfNotAlreadyThere (varToControl); | |||
| else | |||
| arr->remove (arr->indexOf (varToControl)); | |||
| } | |||
| } | |||
| private: | |||
| ValueWithDefault valueWithDefault; | |||
| Value sourceValue; | |||
| var varToControl; | |||
| void valueChanged (Value&) override { sendChangeMessage (true); } | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiChoiceRemapperSourceWithDefault) | |||
| }; | |||
| //============================================================================== | |||
| MultiChoicePropertyComponent::MultiChoicePropertyComponent (const String& propertyName, | |||
| const StringArray& choices, | |||
| const Array<var>& correspondingValues) | |||
| : PropertyComponent (propertyName, 70) | |||
| { | |||
| // The array of corresponding values must contain one value for each of the items in | |||
| // the choices array! | |||
| jassert (choices.size() == correspondingValues.size()); | |||
| ignoreUnused (correspondingValues); | |||
| for (auto choice : choices) | |||
| addAndMakeVisible (choiceButtons.add (new ToggleButton (choice))); | |||
| maxHeight = (choiceButtons.size() * 25) + 20; | |||
| { | |||
| Path expandShape; | |||
| expandShape.addTriangle ({ 0, 0 }, { 5, 10 }, { 10, 0}); | |||
| expandButton.setShape (expandShape, true, true, false); | |||
| } | |||
| expandButton.onClick = [this] { setExpanded (! expanded); }; | |||
| addAndMakeVisible (expandButton); | |||
| lookAndFeelChanged(); | |||
| } | |||
| MultiChoicePropertyComponent::MultiChoicePropertyComponent (const Value& valueToControl, | |||
| const String& propertyName, | |||
| const StringArray& choices, | |||
| const Array<var>& correspondingValues) | |||
| : MultiChoicePropertyComponent (propertyName, choices, correspondingValues) | |||
| { | |||
| // The value to control must be an array! | |||
| jassert (valueToControl.getValue().isArray()); | |||
| for (int i = 0; i < choiceButtons.size(); ++i) | |||
| choiceButtons[i]->getToggleStateValue().referTo (Value (new MultiChoiceRemapperSource (valueToControl, | |||
| correspondingValues[i]))); | |||
| } | |||
| MultiChoicePropertyComponent::MultiChoicePropertyComponent (const ValueWithDefault& valueToControl, | |||
| const String& propertyName, | |||
| const StringArray& choices, | |||
| const Array<var>& correspondingValues) | |||
| : MultiChoicePropertyComponent (propertyName, choices, correspondingValues) | |||
| { | |||
| // The value to control must be an array! | |||
| jassert (valueToControl.get().isArray()); | |||
| for (int i = 0; i < choiceButtons.size(); ++i) | |||
| choiceButtons[i]->getToggleStateValue().referTo (Value (new MultiChoiceRemapperSourceWithDefault (valueToControl, | |||
| correspondingValues[i]))); | |||
| } | |||
| void MultiChoicePropertyComponent::paint (Graphics& g) | |||
| { | |||
| g.setColour (findColour (TextEditor::backgroundColourId)); | |||
| g.fillRect (getLookAndFeel().getPropertyComponentContentPosition (*this)); | |||
| if (! expanded) | |||
| { | |||
| g.setColour (findColour (PropertyComponent::labelTextColourId).withAlpha (0.4f)); | |||
| g.drawFittedText ("+ " + String (numHidden) + " more", getLookAndFeel().getPropertyComponentContentPosition (*this) | |||
| .removeFromBottom (20).withTrimmedLeft (10), | |||
| Justification::centredLeft, 1); | |||
| } | |||
| PropertyComponent::paint (g); | |||
| } | |||
| void MultiChoicePropertyComponent::resized() | |||
| { | |||
| auto bounds = getLookAndFeel().getPropertyComponentContentPosition (*this); | |||
| bounds.removeFromBottom (5); | |||
| expandButton.setBounds (bounds.removeFromBottom (10)); | |||
| numHidden = 0; | |||
| for (auto* b : choiceButtons) | |||
| { | |||
| if (bounds.getHeight() >= 25) | |||
| { | |||
| b->setVisible (true); | |||
| b->setBounds (bounds.removeFromTop (25).reduced (5, 2)); | |||
| } | |||
| else | |||
| { | |||
| b->setVisible (false); | |||
| ++numHidden; | |||
| } | |||
| } | |||
| } | |||
| void MultiChoicePropertyComponent::setExpanded (bool isExpanded) noexcept | |||
| { | |||
| if (expanded == isExpanded) | |||
| return; | |||
| expanded = isExpanded; | |||
| preferredHeight = expanded ? maxHeight : 70; | |||
| if (auto* propertyPanel = findParentComponentOfClass<PropertyPanel>()) | |||
| propertyPanel->resized(); | |||
| if (onHeightChange != nullptr) | |||
| onHeightChange(); | |||
| expandButton.setTransform (AffineTransform::rotation (expanded ? MathConstants<float>::pi : MathConstants<float>::twoPi, | |||
| (float) expandButton.getBounds().getCentreX(), | |||
| (float) expandButton.getBounds().getCentreY())); | |||
| } | |||
| //============================================================================== | |||
| void MultiChoicePropertyComponent::lookAndFeelChanged() | |||
| { | |||
| auto iconColour = findColour (PropertyComponent::labelTextColourId); | |||
| expandButton.setColours (iconColour, iconColour.darker(), iconColour.darker()); | |||
| } | |||
| } // namespace juce | |||
| @@ -0,0 +1,121 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2017 - ROLI Ltd. | |||
| JUCE is an open source library subject to commercial or open-source | |||
| licensing. | |||
| By using JUCE, you agree to the terms of both the JUCE 5 End-User License | |||
| Agreement and JUCE 5 Privacy Policy (both updated and effective as of the | |||
| 27th April 2017). | |||
| End User License Agreement: www.juce.com/juce-5-licence | |||
| Privacy Policy: www.juce.com/juce-5-privacy-policy | |||
| Or: You may also use this code under the terms of the GPL v3 (see | |||
| www.gnu.org/licenses). | |||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||
| DISCLAIMED. | |||
| ============================================================================== | |||
| */ | |||
| namespace juce | |||
| { | |||
| //============================================================================== | |||
| /** | |||
| A PropertyComponent that shows its value as an expandable list of ToggleButtons. | |||
| This type of property component contains a list of options where multiple options | |||
| can be selected at once. | |||
| @see PropertyComponent, PropertyPanel | |||
| @tags{GUI} | |||
| */ | |||
| class MultiChoicePropertyComponent : public PropertyComponent | |||
| { | |||
| public: | |||
| /** Creates the component. Note that the underlying var object that the Value refers to must be an array. | |||
| @param valueToControl the value that the ToggleButtons will read and control | |||
| @param propertyName the name of the property | |||
| @param choices the list of possible values that will be represented | |||
| @param correspondingValues a list of values corresponding to each item in the 'choices' StringArray. | |||
| These are the values that will be read and written to the | |||
| valueToControl value. This array must contain the same number of items | |||
| as the choices array | |||
| */ | |||
| MultiChoicePropertyComponent (const Value& valueToControl, | |||
| const String& propertyName, | |||
| const StringArray& choices, | |||
| const Array<var>& correspondingValues); | |||
| /** Creates the component using a ValueWithDefault object. This will select the default options. | |||
| @param valueToControl the ValueWithDefault object that contains the Value object that the ToggleButtons will read and control | |||
| @param propertyName the name of the property | |||
| @param choices the list of possible values that will be represented | |||
| @param correspondingValues a list of values corresponding to each item in the 'choices' StringArray. | |||
| These are the values that will be read and written to the | |||
| valueToControl value. This array must contain the same number of items | |||
| as the choices array | |||
| */ | |||
| MultiChoicePropertyComponent (const ValueWithDefault& valueToControl, | |||
| const String& propertyName, | |||
| const StringArray& choices, | |||
| const Array<var>& correspondingValues); | |||
| //============================================================================== | |||
| /** Returns true if the list of options is expanded. */ | |||
| bool isExpanded() const noexcept { return expanded; } | |||
| /** Expands or shrinks the list of options. | |||
| N.B. This will just set the preferredHeight value of the PropertyComponent and attempt to | |||
| call PropertyPanel::resized(), so if you are not displaying this object in a PropertyPanel | |||
| then you should use the onHeightChange callback to resize it when the height changes. | |||
| @see onHeightChange | |||
| */ | |||
| void setExpanded (bool expanded) noexcept; | |||
| /** You can assign a lambda to this callback object to have it called when the MultiChoicePropertyComponent height changes. */ | |||
| std::function<void()> onHeightChange; | |||
| //============================================================================== | |||
| /** @internal */ | |||
| void paint (Graphics& g) override; | |||
| /** @internal */ | |||
| void resized() override; | |||
| /** @internal */ | |||
| void refresh() override {} | |||
| private: | |||
| MultiChoicePropertyComponent (const String&, const StringArray&, const Array<var>&); | |||
| class MultiChoiceRemapperSource; | |||
| class MultiChoiceRemapperSourceWithDefault; | |||
| //============================================================================== | |||
| void lookAndFeelChanged() override; | |||
| //============================================================================== | |||
| int maxHeight = 0; | |||
| int numHidden = 0; | |||
| bool expanded = false; | |||
| OwnedArray<ToggleButton> choiceButtons; | |||
| ShapeButton expandButton { "Expand", Colours::transparentBlack, Colours::transparentBlack, Colours::transparentBlack }; | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiChoicePropertyComponent) | |||
| }; | |||
| } // namespace juce | |||