2 ==============================================================================
4 This file is part of the JUCE library.
5 Copyright (c) 2022 - Raw Material Software Limited
7 JUCE is an open source library subject to commercial or open-source
10 By using JUCE, you agree to the terms of both the JUCE 7 End-User License
11 Agreement and JUCE Privacy Policy.
13 End User License Agreement: www.juce.com/juce-7-licence
14 Privacy Policy: www.juce.com/juce-privacy-policy
16 Or: You may also use this code under the terms of the GPL v3 (see
17 www.gnu.org/licenses).
19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
23 ==============================================================================
30 //==============================================================================
31 class StringComparator
34 static int compareElements (var first
, var second
)
36 if (first
.toString() > second
.toString())
38 else if (first
.toString() < second
.toString())
45 static void updateButtonTickColour (ToggleButton
* button
, bool usingDefault
)
47 button
->setColour (ToggleButton::tickColourId
, button
->getLookAndFeel().findColour (ToggleButton::tickColourId
)
48 .withAlpha (usingDefault
? 0.4f
: 1.0f
));
51 //==============================================================================
52 class MultiChoicePropertyComponent::MultiChoiceRemapperSource
: public Value::ValueSource
,
53 private Value::Listener
56 MultiChoiceRemapperSource (const Value
& source
, var v
, int c
)
57 : sourceValue (source
),
61 sourceValue
.addListener (this);
64 var
getValue() const override
66 if (auto* arr
= sourceValue
.getValue().getArray())
67 if (arr
->contains (varToControl
))
73 void setValue (const var
& newValue
) override
75 if (auto* arr
= sourceValue
.getValue().getArray())
79 if (static_cast<bool> (newValue
))
81 if (temp
.addIfNotAlreadyThere (varToControl
) && (maxChoices
!= -1) && (temp
.size() > maxChoices
))
82 temp
.remove (temp
.size() - 2);
86 temp
.remove (arr
->indexOf (varToControl
));
102 //==============================================================================
103 void valueChanged (Value
&) override
{ sendChangeMessage (true); }
105 //==============================================================================
106 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiChoiceRemapperSource
)
109 //==============================================================================
110 class MultiChoicePropertyComponent::MultiChoiceRemapperSourceWithDefault
: public Value::ValueSource
,
111 private Value::Listener
114 MultiChoiceRemapperSourceWithDefault (const ValueTreePropertyWithDefault
& val
,
115 var v
, int c
, ToggleButton
* b
)
118 sourceValue (value
.getPropertyAsValue()),
122 sourceValue
.addListener (this);
125 var
getValue() const override
127 auto v
= value
.get();
129 if (auto* arr
= v
.getArray())
131 if (arr
->contains (varToControl
))
133 updateButtonTickColour (buttonToControl
, value
.isUsingDefault());
141 void setValue (const var
& newValue
) override
143 auto v
= value
.get();
145 OptionalScopedPointer
<Array
<var
>> arrayToControl
;
147 if (value
.isUsingDefault())
148 arrayToControl
.set (new Array
<var
>(), true); // use an empty array so the default values are overwritten
150 arrayToControl
.set (v
.getArray(), false);
152 if (arrayToControl
!= nullptr)
154 auto temp
= *arrayToControl
;
156 bool newState
= newValue
;
158 if (value
.isUsingDefault())
160 if (auto* defaultArray
= v
.getArray())
162 if (defaultArray
->contains (varToControl
))
163 newState
= true; // force the state as the user is setting it explicitly
169 if (temp
.addIfNotAlreadyThere (varToControl
) && (maxChoices
!= -1) && (temp
.size() > maxChoices
))
170 temp
.remove (temp
.size() - 2);
174 temp
.remove (temp
.indexOf (varToControl
));
182 if (temp
.size() == 0)
183 value
.resetToDefault();
188 //==============================================================================
189 void valueChanged (Value
&) override
{ sendChangeMessage (true); }
191 //==============================================================================
192 ValueTreePropertyWithDefault value
;
198 ToggleButton
* buttonToControl
;
200 //==============================================================================
201 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiChoiceRemapperSourceWithDefault
)
204 //==============================================================================
205 int MultiChoicePropertyComponent::getTotalButtonsHeight (int numButtons
)
207 return numButtons
* buttonHeight
+ 1;
210 MultiChoicePropertyComponent::MultiChoicePropertyComponent (const String
& propertyName
,
211 const StringArray
& choices
,
212 const Array
<var
>& correspondingValues
)
213 : PropertyComponent (propertyName
, jmin (getTotalButtonsHeight (choices
.size()), collapsedHeight
))
215 // The array of corresponding values must contain one value for each of the items in
216 // the choices array!
217 jassertquiet (choices
.size() == correspondingValues
.size());
219 for (auto choice
: choices
)
220 addAndMakeVisible (choiceButtons
.add (new ToggleButton (choice
)));
222 if (preferredHeight
>= collapsedHeight
)
225 maxHeight
= getTotalButtonsHeight (choiceButtons
.size()) + expandAreaHeight
;
232 expandShape
.addTriangle ({ 0, 0 }, { 5, 10 }, { 10, 0});
233 expandButton
.setShape (expandShape
, true, true, false);
236 expandButton
.onClick
= [this] { setExpanded (! expanded
); };
237 addAndMakeVisible (expandButton
);
239 lookAndFeelChanged();
243 MultiChoicePropertyComponent::MultiChoicePropertyComponent (const Value
& valueToControl
,
244 const String
& propertyName
,
245 const StringArray
& choices
,
246 const Array
<var
>& correspondingValues
,
248 : MultiChoicePropertyComponent (propertyName
, choices
, correspondingValues
)
250 // The value to control must be an array!
251 jassert (valueToControl
.getValue().isArray());
253 for (int i
= 0; i
< choiceButtons
.size(); ++i
)
254 choiceButtons
[i
]->getToggleStateValue().referTo (Value (new MultiChoiceRemapperSource (valueToControl
,
255 correspondingValues
[i
],
259 MultiChoicePropertyComponent::MultiChoicePropertyComponent (const ValueTreePropertyWithDefault
& valueToControl
,
260 const String
& propertyName
,
261 const StringArray
& choices
,
262 const Array
<var
>& correspondingValues
,
264 : MultiChoicePropertyComponent (propertyName
, choices
, correspondingValues
)
266 value
= valueToControl
;
268 // The value to control must be an array!
269 jassert (value
.get().isArray());
271 for (int i
= 0; i
< choiceButtons
.size(); ++i
)
272 choiceButtons
[i
]->getToggleStateValue().referTo (Value (new MultiChoiceRemapperSourceWithDefault (value
,
273 correspondingValues
[i
],
277 value
.onDefaultChange
= [this] { repaint(); };
280 void MultiChoicePropertyComponent::paint (Graphics
& g
)
282 g
.setColour (findColour (TextEditor::backgroundColourId
));
283 g
.fillRect (getLookAndFeel().getPropertyComponentContentPosition (*this));
285 if (isExpandable() && ! isExpanded())
287 g
.setColour (findColour (TextEditor::backgroundColourId
).contrasting().withAlpha (0.4f
));
288 g
.drawFittedText ("+ " + String (numHidden
) + " more", getLookAndFeel().getPropertyComponentContentPosition (*this)
289 .removeFromBottom (expandAreaHeight
).withTrimmedLeft (10),
290 Justification::centredLeft
, 1);
293 PropertyComponent::paint (g
);
296 void MultiChoicePropertyComponent::resized()
298 auto bounds
= getLookAndFeel().getPropertyComponentContentPosition (*this);
302 bounds
.removeFromBottom (5);
304 auto buttonSlice
= bounds
.removeFromBottom (10);
305 expandButton
.setSize (10, 10);
306 expandButton
.setCentrePosition (buttonSlice
.getCentre());
311 for (auto* b
: choiceButtons
)
313 if (bounds
.getHeight() >= buttonHeight
)
315 b
->setVisible (true);
316 b
->setBounds (bounds
.removeFromTop (buttonHeight
).reduced (5, 2));
320 b
->setVisible (false);
326 void MultiChoicePropertyComponent::setExpanded (bool shouldBeExpanded
) noexcept
328 if (! isExpandable() || (isExpanded() == shouldBeExpanded
))
331 expanded
= shouldBeExpanded
;
332 preferredHeight
= expanded
? maxHeight
: collapsedHeight
;
334 if (auto* propertyPanel
= findParentComponentOfClass
<PropertyPanel
>())
335 propertyPanel
->resized();
337 if (onHeightChange
!= nullptr)
340 expandButton
.setTransform (AffineTransform::rotation (expanded
? MathConstants
<float>::pi
: MathConstants
<float>::twoPi
,
341 (float) expandButton
.getBounds().getCentreX(),
342 (float) expandButton
.getBounds().getCentreY()));
347 //==============================================================================
348 void MultiChoicePropertyComponent::lookAndFeelChanged()
350 auto iconColour
= findColour (TextEditor::backgroundColourId
).contrasting();
351 expandButton
.setColours (iconColour
, iconColour
.darker(), iconColour
.darker());
353 const auto usingDefault
= value
.isUsingDefault();
355 for (auto* button
: choiceButtons
)
356 updateButtonTickColour (button
, usingDefault
);