git subrepo clone --branch=sono6good https://github.com/essej/JUCE.git deps/juce

subrepo:
  subdir:   "deps/juce"
  merged:   "b13f9084e"
upstream:
  origin:   "https://github.com/essej/JUCE.git"
  branch:   "sono6good"
  commit:   "b13f9084e"
git-subrepo:
  version:  "0.4.3"
  origin:   "https://github.com/ingydotnet/git-subrepo.git"
  commit:   "2f68596"
This commit is contained in:
essej
2022-04-18 17:51:22 -04:00
parent 63e175fee6
commit 25bd5d8adb
3210 changed files with 1045392 additions and 0 deletions

View File

@ -0,0 +1,86 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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
{
BooleanPropertyComponent::BooleanPropertyComponent (const String& name,
const String& buttonTextWhenTrue,
const String& buttonTextWhenFalse)
: PropertyComponent (name),
onText (buttonTextWhenTrue),
offText (buttonTextWhenFalse)
{
addAndMakeVisible (button);
button.setClickingTogglesState (false);
button.onClick = [this] { setState (! getState()); };
}
BooleanPropertyComponent::BooleanPropertyComponent (const Value& valueToControl,
const String& name,
const String& buttonText)
: PropertyComponent (name),
onText (buttonText),
offText (buttonText)
{
addAndMakeVisible (button);
button.setClickingTogglesState (false);
button.setButtonText (buttonText);
button.getToggleStateValue().referTo (valueToControl);
button.setClickingTogglesState (true);
}
BooleanPropertyComponent::~BooleanPropertyComponent()
{
}
void BooleanPropertyComponent::setState (const bool newState)
{
button.setToggleState (newState, sendNotification);
}
bool BooleanPropertyComponent::getState() const
{
return button.getToggleState();
}
void BooleanPropertyComponent::paint (Graphics& g)
{
PropertyComponent::paint (g);
g.setColour (findColour (backgroundColourId));
g.fillRect (button.getBounds());
g.setColour (findColour (outlineColourId));
g.drawRect (button.getBounds());
}
void BooleanPropertyComponent::refresh()
{
button.setToggleState (getState(), dontSendNotification);
button.setButtonText (button.getToggleState() ? onText : offText);
}
} // namespace juce

View File

@ -0,0 +1,109 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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 contains an on/off toggle button.
This type of property component can be used if you have a boolean value to
toggle on/off.
@see PropertyComponent
@tags{GUI}
*/
class JUCE_API BooleanPropertyComponent : public PropertyComponent
{
protected:
//==============================================================================
/** Creates a button component.
If you use this constructor, you must override the getState() and setState()
methods.
@param propertyName the property name to be passed to the PropertyComponent
@param buttonTextWhenTrue the text shown in the button when the value is true
@param buttonTextWhenFalse the text shown in the button when the value is false
*/
BooleanPropertyComponent (const String& propertyName,
const String& buttonTextWhenTrue,
const String& buttonTextWhenFalse);
public:
/** Creates a button component.
Note that if you call this constructor then you must use the Value to interact with the
button state, and you can't override the class with your own setState or getState methods.
If you want to use getState and setState, call the other constructor instead.
@param valueToControl a Value object that this property should refer to.
@param propertyName the property name to be passed to the PropertyComponent
@param buttonText the text shown in the ToggleButton component
*/
BooleanPropertyComponent (const Value& valueToControl,
const String& propertyName,
const String& buttonText);
/** Destructor. */
~BooleanPropertyComponent() override;
//==============================================================================
/** Called to change the state of the boolean value. */
virtual void setState (bool newState);
/** Must return the current value of the property. */
virtual bool getState() const;
//==============================================================================
/** A set of colour IDs to use to change the colour of various aspects of the component.
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
methods.
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
*/
enum ColourIds
{
backgroundColourId = 0x100e801, /**< The colour to fill the background of the button area. */
outlineColourId = 0x100e803, /**< The colour to use to draw an outline around the text area. */
};
//==============================================================================
/** @internal */
void paint (Graphics&) override;
/** @internal */
void refresh() override;
private:
ToggleButton button;
String onText, offText;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BooleanPropertyComponent)
};
} // namespace juce

View File

@ -0,0 +1,46 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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
{
ButtonPropertyComponent::ButtonPropertyComponent (const String& name, bool triggerOnMouseDown)
: PropertyComponent (name)
{
addAndMakeVisible (button);
button.setTriggeredOnMouseDown (triggerOnMouseDown);
button.onClick = [this] { buttonClicked(); };
}
ButtonPropertyComponent::~ButtonPropertyComponent()
{
}
void ButtonPropertyComponent::refresh()
{
button.setButtonText (getButtonText());
}
} // namespace juce

View File

@ -0,0 +1,76 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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 contains a button.
This type of property component can be used if you need a button to trigger some
kind of action.
@see PropertyComponent
@tags{GUI}
*/
class JUCE_API ButtonPropertyComponent : public PropertyComponent
{
public:
//==============================================================================
/** Creates a button component.
@param propertyName the property name to be passed to the PropertyComponent
@param triggerOnMouseDown this is passed to the Button::setTriggeredOnMouseDown() method
*/
ButtonPropertyComponent (const String& propertyName,
bool triggerOnMouseDown);
/** Destructor. */
~ButtonPropertyComponent() override;
//==============================================================================
/** Called when the user clicks the button.
*/
virtual void buttonClicked() = 0;
/** Returns the string that should be displayed in the button.
If you need to change this string, call refresh() to update the component.
*/
virtual String getButtonText() const = 0;
//==============================================================================
/** @internal */
void refresh() override;
private:
TextButton button;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ButtonPropertyComponent)
};
} // namespace juce

View File

@ -0,0 +1,284 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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 ChoicePropertyComponent::RemapperValueSource : public Value::ValueSource,
private Value::Listener
{
public:
RemapperValueSource (const Value& source, const Array<var>& map)
: sourceValue (source),
mappings (map)
{
sourceValue.addListener (this);
}
var getValue() const override
{
auto targetValue = sourceValue.getValue();
for (auto& map : mappings)
if (map.equalsWithSameType (targetValue))
return mappings.indexOf (map) + 1;
return mappings.indexOf (targetValue) + 1;
}
void setValue (const var& newValue) override
{
auto remappedVal = mappings [static_cast<int> (newValue) - 1];
if (! remappedVal.equalsWithSameType (sourceValue))
sourceValue = remappedVal;
}
protected:
Value sourceValue;
Array<var> mappings;
void valueChanged (Value&) override { sendChangeMessage (true); }
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RemapperValueSource)
};
//==============================================================================
class ChoicePropertyComponent::RemapperValueSourceWithDefault : public Value::ValueSource,
private Value::Listener
{
public:
RemapperValueSourceWithDefault (ValueWithDefault* vwd, const Array<var>& map)
: valueWithDefault (vwd),
sourceValue (valueWithDefault->getPropertyAsValue()),
mappings (map)
{
sourceValue.addListener (this);
}
var getValue() const override
{
if (valueWithDefault != nullptr && ! valueWithDefault->isUsingDefault())
{
const auto target = sourceValue.getValue();
const auto equalsWithSameType = [&target] (const var& map) { return map.equalsWithSameType (target); };
auto iter = std::find_if (mappings.begin(), mappings.end(), equalsWithSameType);
if (iter == mappings.end())
iter = std::find (mappings.begin(), mappings.end(), target);
if (iter != mappings.end())
return 1 + (int) std::distance (mappings.begin(), iter);
}
return -1;
}
void setValue (const var& newValue) override
{
if (valueWithDefault == nullptr)
return;
auto newValueInt = static_cast<int> (newValue);
if (newValueInt == -1)
{
valueWithDefault->resetToDefault();
}
else
{
auto remappedVal = mappings [newValueInt - 1];
if (! remappedVal.equalsWithSameType (sourceValue))
*valueWithDefault = remappedVal;
}
}
private:
void valueChanged (Value&) override { sendChangeMessage (true); }
WeakReference<ValueWithDefault> valueWithDefault;
Value sourceValue;
Array<var> mappings;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RemapperValueSourceWithDefault)
};
//==============================================================================
ChoicePropertyComponent::ChoicePropertyComponent (const String& name)
: PropertyComponent (name),
isCustomClass (true)
{
}
ChoicePropertyComponent::ChoicePropertyComponent (const String& name,
const StringArray& choiceList,
const Array<var>& correspondingValues)
: PropertyComponent (name),
choices (choiceList)
{
// The array of corresponding values must contain one value for each of the items in
// the choices array!
jassertquiet (correspondingValues.size() == choices.size());
}
ChoicePropertyComponent::ChoicePropertyComponent (const Value& valueToControl,
const String& name,
const StringArray& choiceList,
const Array<var>& correspondingValues)
: ChoicePropertyComponent (name, choiceList, correspondingValues)
{
refreshChoices();
initialiseComboBox (Value (new RemapperValueSource (valueToControl, correspondingValues)));
}
ChoicePropertyComponent::ChoicePropertyComponent (ValueWithDefault& valueToControl,
const String& name,
const StringArray& choiceList,
const Array<var>& correspondingValues)
: ChoicePropertyComponent (name, choiceList, correspondingValues)
{
valueWithDefault = &valueToControl;
auto getDefaultString = [this, correspondingValues] { return choices [correspondingValues.indexOf (valueWithDefault->getDefault())]; };
refreshChoices (getDefaultString());
initialiseComboBox (Value (new RemapperValueSourceWithDefault (valueWithDefault, correspondingValues)));
valueWithDefault->onDefaultChange = [this, getDefaultString]
{
auto selectedId = comboBox.getSelectedId();
refreshChoices (getDefaultString());
comboBox.setSelectedId (selectedId);
};
}
ChoicePropertyComponent::ChoicePropertyComponent (ValueWithDefault& valueToControl,
const String& name)
: PropertyComponent (name),
choices ({ "Enabled", "Disabled" })
{
valueWithDefault = &valueToControl;
auto getDefaultString = [this] { return valueWithDefault->getDefault() ? "Enabled" : "Disabled"; };
refreshChoices (getDefaultString());
initialiseComboBox (Value (new RemapperValueSourceWithDefault (valueWithDefault, { true, false })));
valueWithDefault->onDefaultChange = [this, getDefaultString]
{
auto selectedId = comboBox.getSelectedId();
refreshChoices (getDefaultString());
comboBox.setSelectedId (selectedId);
};
}
ChoicePropertyComponent::~ChoicePropertyComponent()
{
if (valueWithDefault != nullptr)
valueWithDefault->onDefaultChange = nullptr;
}
//==============================================================================
void ChoicePropertyComponent::initialiseComboBox (const Value& v)
{
if (v != Value())
{
comboBox.setSelectedId (v.getValue(), dontSendNotification);
comboBox.getSelectedIdAsValue().referTo (v);
}
comboBox.setEditableText (false);
addAndMakeVisible (comboBox);
}
void ChoicePropertyComponent::refreshChoices()
{
comboBox.clear();
for (auto choice : choices)
{
if (choice.isNotEmpty())
comboBox.addItem (choice, choices.indexOf (choice) + 1);
else
comboBox.addSeparator();
}
}
void ChoicePropertyComponent::refreshChoices (const String& defaultString)
{
refreshChoices();
comboBox.addItem ("Default" + (defaultString.isNotEmpty() ? " (" + defaultString + ")" : ""), -1);
}
//==============================================================================
void ChoicePropertyComponent::setIndex (const int /*newIndex*/)
{
jassertfalse; // you need to override this method in your subclass!
}
int ChoicePropertyComponent::getIndex() const
{
jassertfalse; // you need to override this method in your subclass!
return -1;
}
const StringArray& ChoicePropertyComponent::getChoices() const
{
return choices;
}
//==============================================================================
void ChoicePropertyComponent::refresh()
{
if (isCustomClass)
{
if (! comboBox.isVisible())
{
refreshChoices();
initialiseComboBox ({});
comboBox.onChange = [this] { changeIndex(); };
}
comboBox.setSelectedId (getIndex() + 1, dontSendNotification);
}
}
void ChoicePropertyComponent::changeIndex()
{
if (isCustomClass)
{
auto newIndex = comboBox.getSelectedId() - 1;
if (newIndex != getIndex())
setIndex (newIndex);
}
}
} // namespace juce

View File

@ -0,0 +1,162 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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 a combo box.
This type of property component contains a list of options and has a
combo box to choose one.
Your subclass's constructor must add some strings to the choices StringArray
and these are shown in the list.
The getIndex() method will be called to find out which option is the currently
selected one. If you call refresh() it will call getIndex() to check whether
the value has changed, and will update the combo box if needed.
If the user selects a different item from the list, setIndex() will be
called to let your class process this.
@see PropertyComponent, PropertyPanel
@tags{GUI}
*/
class JUCE_API ChoicePropertyComponent : public PropertyComponent
{
private:
/** Delegating constructor. */
ChoicePropertyComponent (const String&, const StringArray&, const Array<var>&);
protected:
/** Creates the component.
Your subclass's constructor must add a list of options to the choices member variable.
*/
ChoicePropertyComponent (const String& propertyName);
public:
/** Creates the component.
Note that if you call this constructor then you must use the Value to interact with the
index, and you can't override the class with your own setIndex or getIndex methods.
If you want to use those methods, call the other constructor instead.
@param valueToControl the value that the combo box will read and control
@param propertyName the name of the property
@param choices the list of possible values that the drop-down list will contain
@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
*/
ChoicePropertyComponent (const Value& valueToControl,
const String& propertyName,
const StringArray& choices,
const Array<var>& correspondingValues);
/** Creates the component using a ValueWithDefault object. This will add an item to the ComboBox for the
default value with an ID of -1.
@param valueToControl the ValueWithDefault object that contains the Value object that the combo box will read and control.
@param propertyName the name of the property
@param choices the list of possible values that the drop-down list will contain
@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
*/
ChoicePropertyComponent (ValueWithDefault& valueToControl,
const String& propertyName,
const StringArray& choices,
const Array<var>& correspondingValues);
/** Creates the component using a ValueWithDefault object, adding an item to the ComboBox for the
default value with an ID of -1 as well as adding separate "Enabled" and "Disabled" options.
This is useful for simple on/off choices that also need a default value.
*/
ChoicePropertyComponent (ValueWithDefault& valueToControl,
const String& propertyName);
/** Destructor. */
~ChoicePropertyComponent() override;
//==============================================================================
/** Called when the user selects an item from the combo box.
Your subclass must use this callback to update the value that this component
represents. The index is the index of the chosen item in the choices
StringArray.
*/
virtual void setIndex (int newIndex);
/** Returns the index of the item that should currently be shown.
This is the index of the item in the choices StringArray that will be shown.
*/
virtual int getIndex() const;
/** Returns the list of options. */
const StringArray& getChoices() const;
//==============================================================================
/** @internal */
void refresh() override;
protected:
/** The list of options that will be shown in the combo box.
Your subclass must populate this array in its constructor. If any empty
strings are added, these will be replaced with horizontal separators (see
ComboBox::addSeparator() for more info).
*/
StringArray choices;
private:
//==============================================================================
class RemapperValueSource;
class RemapperValueSourceWithDefault;
//==============================================================================
void initialiseComboBox (const Value&);
void refreshChoices();
void refreshChoices (const String&);
void changeIndex();
//==============================================================================
ComboBox comboBox;
bool isCustomClass = false;
WeakReference<ValueWithDefault> valueWithDefault;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChoicePropertyComponent)
};
} // namespace juce

View File

@ -0,0 +1,373 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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 StringComparator
{
public:
static int compareElements (var first, var second)
{
if (first.toString() > second.toString())
return 1;
else if (first.toString() < second.toString())
return -1;
return 0;
}
};
static void updateButtonTickColour (ToggleButton* button, bool usingDefault)
{
button->setColour (ToggleButton::tickColourId, button->getLookAndFeel().findColour (ToggleButton::tickColourId)
.withAlpha (usingDefault ? 0.4f : 1.0f));
}
//==============================================================================
class MultiChoicePropertyComponent::MultiChoiceRemapperSource : public Value::ValueSource,
private Value::Listener
{
public:
MultiChoiceRemapperSource (const Value& source, var v, int c)
: sourceValue (source),
varToControl (v),
maxChoices (c)
{
sourceValue.addListener (this);
}
var getValue() const override
{
if (auto* arr = sourceValue.getValue().getArray())
if (arr->contains (varToControl))
return true;
return false;
}
void setValue (const var& newValue) override
{
if (auto* arr = sourceValue.getValue().getArray())
{
auto temp = *arr;
if (static_cast<bool> (newValue))
{
if (temp.addIfNotAlreadyThere (varToControl) && (maxChoices != -1) && (temp.size() > maxChoices))
temp.remove (temp.size() - 2);
}
else
{
temp.remove (arr->indexOf (varToControl));
}
StringComparator c;
temp.sort (c);
sourceValue = temp;
}
}
private:
Value sourceValue;
var varToControl;
int maxChoices;
//==============================================================================
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 (ValueWithDefault* vwd, var v, int c, ToggleButton* b)
: valueWithDefault (vwd),
varToControl (v),
sourceValue (valueWithDefault->getPropertyAsValue()),
maxChoices (c),
buttonToControl (b)
{
sourceValue.addListener (this);
}
var getValue() const override
{
if (valueWithDefault == nullptr)
return {};
auto v = valueWithDefault->get();
if (auto* arr = v.getArray())
{
if (arr->contains (varToControl))
{
updateButtonTickColour (buttonToControl, valueWithDefault->isUsingDefault());
return true;
}
}
return false;
}
void setValue (const var& newValue) override
{
if (valueWithDefault == nullptr)
return;
auto v = valueWithDefault->get();
OptionalScopedPointer<Array<var>> arrayToControl;
if (valueWithDefault->isUsingDefault())
arrayToControl.set (new Array<var>(), true); // use an empty array so the default values are overwritten
else
arrayToControl.set (v.getArray(), false);
if (arrayToControl != nullptr)
{
auto temp = *arrayToControl;
bool newState = newValue;
if (valueWithDefault->isUsingDefault())
{
if (auto* defaultArray = v.getArray())
{
if (defaultArray->contains (varToControl))
newState = true; // force the state as the user is setting it explicitly
}
}
if (newState)
{
if (temp.addIfNotAlreadyThere (varToControl) && (maxChoices != -1) && (temp.size() > maxChoices))
temp.remove (temp.size() - 2);
}
else
{
temp.remove (temp.indexOf (varToControl));
}
StringComparator c;
temp.sort (c);
*valueWithDefault = temp;
if (temp.size() == 0)
valueWithDefault->resetToDefault();
}
}
private:
//==============================================================================
void valueChanged (Value&) override { sendChangeMessage (true); }
//==============================================================================
WeakReference<ValueWithDefault> valueWithDefault;
var varToControl;
Value sourceValue;
int maxChoices;
ToggleButton* buttonToControl;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiChoiceRemapperSourceWithDefault)
};
//==============================================================================
int MultiChoicePropertyComponent::getTotalButtonsHeight (int numButtons)
{
return numButtons * buttonHeight + 1;
}
MultiChoicePropertyComponent::MultiChoicePropertyComponent (const String& propertyName,
const StringArray& choices,
const Array<var>& correspondingValues)
: PropertyComponent (propertyName, jmin (getTotalButtonsHeight (choices.size()), collapsedHeight))
{
// The array of corresponding values must contain one value for each of the items in
// the choices array!
jassertquiet (choices.size() == correspondingValues.size());
for (auto choice : choices)
addAndMakeVisible (choiceButtons.add (new ToggleButton (choice)));
if (preferredHeight >= collapsedHeight)
{
expandable = true;
maxHeight = getTotalButtonsHeight (choiceButtons.size()) + expandAreaHeight;
}
if (isExpandable())
{
{
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,
int maxChoices)
: 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],
maxChoices)));
}
MultiChoicePropertyComponent::MultiChoicePropertyComponent (ValueWithDefault& valueToControl,
const String& propertyName,
const StringArray& choices,
const Array<var>& correspondingValues,
int maxChoices)
: MultiChoicePropertyComponent (propertyName, choices, correspondingValues)
{
valueWithDefault = &valueToControl;
// The value to control must be an array!
jassert (valueWithDefault->get().isArray());
for (int i = 0; i < choiceButtons.size(); ++i)
choiceButtons[i]->getToggleStateValue().referTo (Value (new MultiChoiceRemapperSourceWithDefault (valueWithDefault,
correspondingValues[i],
maxChoices,
choiceButtons[i])));
valueWithDefault->onDefaultChange = [this] { repaint(); };
}
MultiChoicePropertyComponent::~MultiChoicePropertyComponent()
{
if (valueWithDefault != nullptr)
valueWithDefault->onDefaultChange = nullptr;
}
void MultiChoicePropertyComponent::paint (Graphics& g)
{
g.setColour (findColour (TextEditor::backgroundColourId));
g.fillRect (getLookAndFeel().getPropertyComponentContentPosition (*this));
if (isExpandable() && ! isExpanded())
{
g.setColour (findColour (TextEditor::backgroundColourId).contrasting().withAlpha (0.4f));
g.drawFittedText ("+ " + String (numHidden) + " more", getLookAndFeel().getPropertyComponentContentPosition (*this)
.removeFromBottom (expandAreaHeight).withTrimmedLeft (10),
Justification::centredLeft, 1);
}
PropertyComponent::paint (g);
}
void MultiChoicePropertyComponent::resized()
{
auto bounds = getLookAndFeel().getPropertyComponentContentPosition (*this);
if (isExpandable())
{
bounds.removeFromBottom (5);
auto buttonSlice = bounds.removeFromBottom (10);
expandButton.setSize (10, 10);
expandButton.setCentrePosition (buttonSlice.getCentre());
}
numHidden = 0;
for (auto* b : choiceButtons)
{
if (bounds.getHeight() >= buttonHeight)
{
b->setVisible (true);
b->setBounds (bounds.removeFromTop (buttonHeight).reduced (5, 2));
}
else
{
b->setVisible (false);
++numHidden;
}
}
}
void MultiChoicePropertyComponent::setExpanded (bool shouldBeExpanded) noexcept
{
if (! isExpandable() || (isExpanded() == shouldBeExpanded))
return;
expanded = shouldBeExpanded;
preferredHeight = expanded ? maxHeight : collapsedHeight;
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()));
resized();
}
//==============================================================================
void MultiChoicePropertyComponent::lookAndFeelChanged()
{
auto iconColour = findColour (TextEditor::backgroundColourId).contrasting();
expandButton.setColours (iconColour, iconColour.darker(), iconColour.darker());
if (valueWithDefault != nullptr)
{
auto usingDefault = valueWithDefault->isUsingDefault();
for (auto* button : choiceButtons)
updateButtonTickColour (button, usingDefault);
}
}
} // namespace juce

View File

@ -0,0 +1,140 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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
{
private:
/** Delegating constructor. */
MultiChoicePropertyComponent (const String&, const StringArray&, const Array<var>&);
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
@param maxChoices the maximum number of values which can be selected at once. The default of
-1 will not limit the number that can be selected
*/
MultiChoicePropertyComponent (const Value& valueToControl,
const String& propertyName,
const StringArray& choices,
const Array<var>& correspondingValues,
int maxChoices = -1);
/** 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
@param maxChoices the maximum number of values which can be selected at once. The default of
-1 will not limit the number that can be selected
*/
MultiChoicePropertyComponent (ValueWithDefault& valueToControl,
const String& propertyName,
const StringArray& choices,
const Array<var>& correspondingValues,
int maxChoices = -1);
~MultiChoicePropertyComponent() override;
//==============================================================================
/** Returns true if the list of options is expanded. */
bool isExpanded() const noexcept { return expanded; }
/** Returns true if the list of options has been truncated and can be expanded. */
bool isExpandable() const noexcept { return expandable; }
/** Expands or shrinks the list of options if they are not all visible.
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:
//==============================================================================
class MultiChoiceRemapperSource;
class MultiChoiceRemapperSourceWithDefault;
//==============================================================================
static int getTotalButtonsHeight (int);
void lookAndFeelChanged() override;
//==============================================================================
WeakReference<ValueWithDefault> valueWithDefault;
static constexpr int collapsedHeight = 125;
static constexpr int buttonHeight = 25;
static constexpr int expandAreaHeight = 20;
int maxHeight = 0, numHidden = 0;
bool expandable = false, expanded = false;
OwnedArray<ToggleButton> choiceButtons;
ShapeButton expandButton { "Expand", Colours::transparentBlack, Colours::transparentBlack, Colours::transparentBlack };
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MultiChoicePropertyComponent)
};
} // namespace juce

View File

@ -0,0 +1,56 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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
{
PropertyComponent::PropertyComponent (const String& name, int height)
: Component (name), preferredHeight (height)
{
jassert (name.isNotEmpty());
}
PropertyComponent::~PropertyComponent() {}
void PropertyComponent::paint (Graphics& g)
{
auto& lf = getLookAndFeel();
lf.drawPropertyComponentBackground (g, getWidth(), getHeight(), *this);
lf.drawPropertyComponentLabel (g, getWidth(), getHeight(), *this);
}
void PropertyComponent::resized()
{
if (auto c = getChildComponent(0))
c->setBounds (getLookAndFeel().getPropertyComponentContentPosition (*this));
}
void PropertyComponent::enablementChanged()
{
repaint();
}
} // namespace juce

View File

@ -0,0 +1,145 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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 base class for a component that goes in a PropertyPanel and displays one of
an item's properties.
Subclasses of this are used to display a property in various forms, e.g. a
ChoicePropertyComponent shows its value as a combo box; a SliderPropertyComponent
shows its value as a slider; a TextPropertyComponent as a text box, etc.
A subclass must implement the refresh() method which will be called to tell the
component to update itself, and is also responsible for calling this it when the
item that it refers to is changed.
@see PropertyPanel, TextPropertyComponent, SliderPropertyComponent,
ChoicePropertyComponent, ButtonPropertyComponent, BooleanPropertyComponent
@tags{GUI}
*/
class JUCE_API PropertyComponent : public Component,
public SettableTooltipClient
{
public:
//==============================================================================
/** Creates a PropertyComponent.
@param propertyName the name is stored as this component's name, and is
used as the name displayed next to this component in
a property panel
@param preferredHeight the height that the component should be given - some
items may need to be larger than a normal row height.
This value can also be set if a subclass changes the
preferredHeight member variable.
*/
PropertyComponent (const String& propertyName,
int preferredHeight = 25);
/** Destructor. */
~PropertyComponent() override;
//==============================================================================
/** Returns this item's preferred height.
This value is specified either in the constructor or by a subclass changing the
preferredHeight member variable.
*/
int getPreferredHeight() const noexcept { return preferredHeight; }
void setPreferredHeight (int newHeight) noexcept { preferredHeight = newHeight; }
//==============================================================================
/** Updates the property component if the item it refers to has changed.
A subclass must implement this method, and other objects may call it to
force it to refresh itself.
The subclass should be economical in the amount of work is done, so for
example it should check whether it really needs to do a repaint rather than
just doing one every time this method is called, as it may be called when
the value being displayed hasn't actually changed.
*/
virtual void refresh() = 0;
/** The default paint method fills the background and draws a label for the
item's name.
@see LookAndFeel::drawPropertyComponentBackground(), LookAndFeel::drawPropertyComponentLabel()
*/
void paint (Graphics&) override;
/** The default resize method positions any child component to the right of this
one, based on the look and feel's default label size.
*/
void resized() override;
/** By default, this just repaints the component. */
void enablementChanged() override;
//==============================================================================
/** A set of colour IDs to use to change the colour of various aspects of the combo box.
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
methods.
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
*/
enum ColourIds
{
backgroundColourId = 0x1008300, /**< The background colour to fill the component with. */
labelTextColourId = 0x1008301, /**< The colour for the property's label text. */
};
//==============================================================================
/** This abstract base class is implemented by LookAndFeel classes. */
struct JUCE_API LookAndFeelMethods
{
virtual ~LookAndFeelMethods() = default;
virtual void drawPropertyPanelSectionHeader (Graphics&, const String& name, bool isOpen, int width, int height) = 0;
virtual void drawPropertyComponentBackground (Graphics&, int width, int height, PropertyComponent&) = 0;
virtual void drawPropertyComponentLabel (Graphics&, int width, int height, PropertyComponent&) = 0;
virtual Rectangle<int> getPropertyComponentContentPosition (PropertyComponent&) = 0;
virtual int getPropertyPanelSectionHeaderHeight (const String& sectionTitle) = 0;
};
protected:
/** Used by the PropertyPanel to determine how high this component needs to be.
A subclass can update this value in its constructor but shouldn't alter it later
as changes won't necessarily be picked up.
*/
int preferredHeight;
private:
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertyComponent)
};
} // namespace juce

View File

@ -0,0 +1,396 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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
{
struct PropertyPanel::SectionComponent : public Component
{
SectionComponent (const String& sectionTitle,
const Array<PropertyComponent*>& newProperties,
bool sectionIsOpen,
int extraPadding)
: Component (sectionTitle),
isOpen (sectionIsOpen),
padding (extraPadding)
{
lookAndFeelChanged();
propertyComps.addArray (newProperties);
for (auto* propertyComponent : propertyComps)
{
addAndMakeVisible (propertyComponent);
propertyComponent->refresh();
}
}
~SectionComponent() override
{
propertyComps.clear();
}
void paint (Graphics& g) override
{
if (titleHeight > 0)
getLookAndFeel().drawPropertyPanelSectionHeader (g, getName(), isOpen, getWidth(), titleHeight);
}
void resized() override
{
auto y = titleHeight;
for (auto* propertyComponent : propertyComps)
{
propertyComponent->setBounds (1, y, getWidth() - 2, propertyComponent->getPreferredHeight());
y = propertyComponent->getBottom() + padding;
}
}
void lookAndFeelChanged() override
{
titleHeight = getLookAndFeel().getPropertyPanelSectionHeaderHeight (getName());
resized();
repaint();
}
int getPreferredHeight() const
{
auto y = titleHeight;
auto numComponents = propertyComps.size();
if (numComponents > 0 && isOpen)
{
for (auto* propertyComponent : propertyComps)
y += propertyComponent->getPreferredHeight();
y += (numComponents - 1) * padding;
}
return y;
}
void setOpen (bool open)
{
if (isOpen != open)
{
isOpen = open;
for (auto* propertyComponent : propertyComps)
propertyComponent->setVisible (open);
if (auto* propertyPanel = findParentComponentOfClass<PropertyPanel>())
propertyPanel->resized();
}
}
void refreshAll() const
{
for (auto* propertyComponent : propertyComps)
propertyComponent->refresh();
}
void mouseUp (const MouseEvent& e) override
{
if (e.getMouseDownX() < titleHeight
&& e.x < titleHeight
&& e.getNumberOfClicks() != 2)
mouseDoubleClick (e);
}
void mouseDoubleClick (const MouseEvent& e) override
{
if (e.y < titleHeight)
setOpen (! isOpen);
}
OwnedArray<PropertyComponent> propertyComps;
int titleHeight;
bool isOpen;
int padding;
JUCE_DECLARE_NON_COPYABLE (SectionComponent)
};
//==============================================================================
struct PropertyPanel::PropertyHolderComponent : public Component
{
PropertyHolderComponent() {}
void paint (Graphics&) override {}
void updateLayout (int width)
{
auto y = 0;
for (auto* section : sections)
{
section->setBounds (0, y, width, section->getPreferredHeight());
y = section->getBottom();
}
setSize (width, y);
repaint();
}
void refreshAll() const
{
for (auto* section : sections)
section->refreshAll();
}
void insertSection (int indexToInsertAt, SectionComponent* newSection)
{
sections.insert (indexToInsertAt, newSection);
addAndMakeVisible (newSection, 0);
}
SectionComponent* getSectionWithNonEmptyName (int targetIndex) const noexcept
{
auto index = 0;
for (auto* section : sections)
{
if (section->getName().isNotEmpty())
if (index++ == targetIndex)
return section;
}
return nullptr;
}
OwnedArray<SectionComponent> sections;
JUCE_DECLARE_NON_COPYABLE (PropertyHolderComponent)
};
//==============================================================================
PropertyPanel::PropertyPanel()
{
init();
}
PropertyPanel::PropertyPanel (const String& name) : Component (name)
{
init();
}
void PropertyPanel::init()
{
messageWhenEmpty = TRANS("(nothing selected)");
addAndMakeVisible (viewport);
viewport.setViewedComponent (propertyHolderComponent = new PropertyHolderComponent());
viewport.setFocusContainerType (FocusContainerType::keyboardFocusContainer);
}
PropertyPanel::~PropertyPanel()
{
clear();
}
//==============================================================================
void PropertyPanel::paint (Graphics& g)
{
if (isEmpty())
{
g.setColour (Colours::black.withAlpha (0.5f));
g.setFont (14.0f);
g.drawText (messageWhenEmpty, getLocalBounds().withHeight (30),
Justification::centred, true);
}
}
void PropertyPanel::resized()
{
viewport.setBounds (getLocalBounds());
updatePropHolderLayout();
}
//==============================================================================
void PropertyPanel::clear()
{
if (! isEmpty())
{
propertyHolderComponent->sections.clear();
updatePropHolderLayout();
}
}
bool PropertyPanel::isEmpty() const
{
return propertyHolderComponent->sections.size() == 0;
}
int PropertyPanel::getTotalContentHeight() const
{
return propertyHolderComponent->getHeight();
}
void PropertyPanel::addProperties (const Array<PropertyComponent*>& newProperties,
int extraPaddingBetweenComponents)
{
if (isEmpty())
repaint();
propertyHolderComponent->insertSection (-1, new SectionComponent ({}, newProperties, true, extraPaddingBetweenComponents));
updatePropHolderLayout();
}
void PropertyPanel::addSection (const String& sectionTitle,
const Array<PropertyComponent*>& newProperties,
bool shouldBeOpen,
int indexToInsertAt,
int extraPaddingBetweenComponents)
{
jassert (sectionTitle.isNotEmpty());
if (isEmpty())
repaint();
propertyHolderComponent->insertSection (indexToInsertAt, new SectionComponent (sectionTitle,
newProperties,
shouldBeOpen,
extraPaddingBetweenComponents));
updatePropHolderLayout();
}
void PropertyPanel::updatePropHolderLayout() const
{
auto maxWidth = viewport.getMaximumVisibleWidth();
propertyHolderComponent->updateLayout (maxWidth);
auto newMaxWidth = viewport.getMaximumVisibleWidth();
if (maxWidth != newMaxWidth)
{
// need to do this twice because of scrollbars changing the size, etc.
propertyHolderComponent->updateLayout (newMaxWidth);
}
}
void PropertyPanel::refreshAll() const
{
propertyHolderComponent->refreshAll();
}
//==============================================================================
StringArray PropertyPanel::getSectionNames() const
{
StringArray s;
for (auto* section : propertyHolderComponent->sections)
{
if (section->getName().isNotEmpty())
s.add (section->getName());
}
return s;
}
bool PropertyPanel::isSectionOpen (int sectionIndex) const
{
if (auto* s = propertyHolderComponent->getSectionWithNonEmptyName (sectionIndex))
return s->isOpen;
return false;
}
void PropertyPanel::setSectionOpen (int sectionIndex, bool shouldBeOpen)
{
if (auto* s = propertyHolderComponent->getSectionWithNonEmptyName (sectionIndex))
s->setOpen (shouldBeOpen);
}
void PropertyPanel::setSectionEnabled (int sectionIndex, bool shouldBeEnabled)
{
if (auto* s = propertyHolderComponent->getSectionWithNonEmptyName (sectionIndex))
s->setEnabled (shouldBeEnabled);
}
void PropertyPanel::removeSection (int sectionIndex)
{
if (auto* s = propertyHolderComponent->getSectionWithNonEmptyName (sectionIndex))
{
propertyHolderComponent->sections.removeObject (s);
updatePropHolderLayout();
}
}
//==============================================================================
std::unique_ptr<XmlElement> PropertyPanel::getOpennessState() const
{
auto xml = std::make_unique<XmlElement> ("PROPERTYPANELSTATE");
xml->setAttribute ("scrollPos", viewport.getViewPositionY());
auto sections = getSectionNames();
for (auto s : sections)
{
if (s.isNotEmpty())
{
auto* e = xml->createNewChildElement ("SECTION");
e->setAttribute ("name", s);
e->setAttribute ("open", isSectionOpen (sections.indexOf (s)) ? 1 : 0);
}
}
return xml;
}
void PropertyPanel::restoreOpennessState (const XmlElement& xml)
{
if (xml.hasTagName ("PROPERTYPANELSTATE"))
{
auto sections = getSectionNames();
for (auto* e : xml.getChildWithTagNameIterator ("SECTION"))
{
setSectionOpen (sections.indexOf (e->getStringAttribute ("name")),
e->getBoolAttribute ("open"));
}
viewport.setViewPosition (viewport.getViewPositionX(),
xml.getIntAttribute ("scrollPos", viewport.getViewPositionY()));
}
}
//==============================================================================
void PropertyPanel::setMessageWhenEmpty (const String& newMessage)
{
if (messageWhenEmpty != newMessage)
{
messageWhenEmpty = newMessage;
repaint();
}
}
const String& PropertyPanel::getMessageWhenEmpty() const noexcept
{
return messageWhenEmpty;
}
} // namespace juce

View File

@ -0,0 +1,177 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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 panel that holds a list of PropertyComponent objects.
This panel displays a list of PropertyComponents, and allows them to be organised
into collapsible sections.
To use, simply create one of these and add your properties to it with addProperties()
or addSection().
@see PropertyComponent
@tags{GUI}
*/
class JUCE_API PropertyPanel : public Component
{
public:
//==============================================================================
/** Creates an empty property panel. */
PropertyPanel();
/** Creates an empty property panel. */
PropertyPanel (const String& name);
/** Destructor. */
~PropertyPanel() override;
//==============================================================================
/** Deletes all property components from the panel. */
void clear();
/** Adds a set of properties to the panel.
The components in the list will be owned by this object and will be automatically
deleted later on when no longer needed.
These properties are added without them being inside a named section. If you
want them to be kept together in a collapsible section, use addSection() instead.
*/
void addProperties (const Array<PropertyComponent*>& newPropertyComponents,
int extraPaddingBetweenComponents = 0);
/** Adds a set of properties to the panel.
These properties are added under a section heading with a plus/minus button that
allows it to be opened and closed. If indexToInsertAt is < 0 then it will be added
at the end of the list, or before the given index if the index is non-zero.
The components in the list will be owned by this object and will be automatically
deleted later on when no longer needed.
To add properties without them being in a section, use addProperties().
*/
void addSection (const String& sectionTitle,
const Array<PropertyComponent*>& newPropertyComponents,
bool shouldSectionInitiallyBeOpen = true,
int indexToInsertAt = -1,
int extraPaddingBetweenComponents = 0);
/** Calls the refresh() method of all PropertyComponents in the panel */
void refreshAll() const;
/** Returns true if the panel contains no properties. */
bool isEmpty() const;
/** Returns the height that the panel needs in order to display all of its content
without scrolling.
*/
int getTotalContentHeight() const;
//==============================================================================
/** Returns a list of all the names of sections in the panel.
These are the sections that have been added with addSection().
*/
StringArray getSectionNames() const;
/** Returns true if the section at this index is currently open.
The index is from 0 up to the number of items returned by getSectionNames().
*/
bool isSectionOpen (int sectionIndex) const;
/** Opens or closes one of the sections.
The index is from 0 up to the number of items returned by getSectionNames().
*/
void setSectionOpen (int sectionIndex, bool shouldBeOpen);
/** Enables or disables one of the sections.
The index is from 0 up to the number of items returned by getSectionNames().
*/
void setSectionEnabled (int sectionIndex, bool shouldBeEnabled);
/** Remove one of the sections using the section index.
The index is from 0 up to the number of items returned by getSectionNames().
*/
void removeSection (int sectionIndex);
//==============================================================================
/** Saves the current state of open/closed sections so it can be restored later.
To restore this state, use restoreOpennessState().
@see restoreOpennessState
*/
std::unique_ptr<XmlElement> getOpennessState() const;
/** Restores a previously saved arrangement of open/closed sections.
This will try to restore a snapshot of the panel's state that was created by
the getOpennessState() method. If any of the sections named in the original
XML aren't present, they will be ignored.
@see getOpennessState
*/
void restoreOpennessState (const XmlElement& newState);
//==============================================================================
/** Sets a message to be displayed when there are no properties in the panel.
The default message is "nothing selected".
*/
void setMessageWhenEmpty (const String& newMessage);
/** Returns the message that is displayed when there are no properties.
@see setMessageWhenEmpty
*/
const String& getMessageWhenEmpty() const noexcept;
//==============================================================================
/** Returns the PropertyPanel's internal Viewport. */
Viewport& getViewport() noexcept { return viewport; }
//==============================================================================
/** @internal */
void paint (Graphics&) override;
/** @internal */
void resized() override;
private:
Viewport viewport;
struct SectionComponent;
struct PropertyHolderComponent;
PropertyHolderComponent* propertyHolderComponent;
String messageWhenEmpty;
void init();
void updatePropHolderLayout() const;
void updatePropHolderLayout (int width) const;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (PropertyPanel)
};
} // namespace juce

View File

@ -0,0 +1,86 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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
{
SliderPropertyComponent::SliderPropertyComponent (const String& name,
const double rangeMin,
const double rangeMax,
const double interval,
const double skewFactor,
bool symmetricSkew)
: PropertyComponent (name)
{
addAndMakeVisible (slider);
slider.setRange (rangeMin, rangeMax, interval);
slider.setSkewFactor (skewFactor, symmetricSkew);
slider.setSliderStyle (Slider::LinearBar);
slider.onValueChange = [this]
{
if (getValue() != slider.getValue())
setValue (slider.getValue());
};
}
SliderPropertyComponent::SliderPropertyComponent (const Value& valueToControl,
const String& name,
const double rangeMin,
const double rangeMax,
const double interval,
const double skewFactor,
bool symmetricSkew)
: PropertyComponent (name)
{
addAndMakeVisible (slider);
slider.setRange (rangeMin, rangeMax, interval);
slider.setSkewFactor (skewFactor, symmetricSkew);
slider.setSliderStyle (Slider::LinearBar);
slider.getValueObject().referTo (valueToControl);
}
SliderPropertyComponent::~SliderPropertyComponent()
{
}
void SliderPropertyComponent::setValue (const double /*newValue*/)
{
}
double SliderPropertyComponent::getValue() const
{
return slider.getValue();
}
void SliderPropertyComponent::refresh()
{
slider.setValue (getValue(), dontSendNotification);
}
} // namespace juce

View File

@ -0,0 +1,107 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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 a slider.
@see PropertyComponent, Slider
@tags{GUI}
*/
class JUCE_API SliderPropertyComponent : public PropertyComponent
{
protected:
//==============================================================================
/** Creates the property component.
The ranges, interval and skew factor are passed to the Slider component.
If you need to customise the slider in other ways, your constructor can
access the slider member variable and change it directly.
*/
SliderPropertyComponent (const String& propertyName,
double rangeMin,
double rangeMax,
double interval,
double skewFactor = 1.0,
bool symmetricSkew = false);
public:
//==============================================================================
/** Creates the property component.
The ranges, interval and skew factor are passed to the Slider component.
If you need to customise the slider in other ways, your constructor can
access the slider member variable and change it directly.
Note that if you call this constructor then you must use the Value to interact with
the value, and you can't override the class with your own setValue or getValue methods.
If you want to use those methods, call the other constructor instead.
*/
SliderPropertyComponent (const Value& valueToControl,
const String& propertyName,
double rangeMin,
double rangeMax,
double interval,
double skewFactor = 1.0,
bool symmetricSkew = false);
/** Destructor. */
~SliderPropertyComponent() override;
//==============================================================================
/** Called when the user moves the slider to change its value.
Your subclass must use this method to update whatever item this property
represents.
*/
virtual void setValue (double newValue);
/** Returns the value that the slider should show. */
virtual double getValue() const;
//==============================================================================
/** @internal */
void refresh() override;
protected:
/** The slider component being used in this component.
Your subclass has access to this in case it needs to customise it in some way.
*/
Slider slider;
private:
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SliderPropertyComponent)
};
} // namespace juce

View File

@ -0,0 +1,266 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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 TextPropertyComponent::LabelComp : public Label,
public FileDragAndDropTarget
{
public:
LabelComp (TextPropertyComponent& tpc, int charLimit, bool multiline, bool editable)
: Label ({}, {}),
owner (tpc),
maxChars (charLimit),
isMultiline (multiline)
{
setEditable (editable, editable);
updateColours();
}
bool isInterestedInFileDrag (const StringArray&) override
{
return interestedInFileDrag;
}
void filesDropped (const StringArray& files, int, int) override
{
setText (getText() + files.joinIntoString (isMultiline ? "\n" : ", "), sendNotificationSync);
showEditor();
}
TextEditor* createEditorComponent() override
{
auto* ed = Label::createEditorComponent();
ed->setInputRestrictions (maxChars);
if (isMultiline)
{
ed->setMultiLine (true, true);
ed->setReturnKeyStartsNewLine (true);
}
return ed;
}
void textWasEdited() override
{
owner.textWasEdited();
}
void updateColours()
{
setColour (backgroundColourId, owner.findColour (TextPropertyComponent::backgroundColourId));
setColour (outlineColourId, owner.findColour (TextPropertyComponent::outlineColourId));
setColour (textColourId, owner.findColour (TextPropertyComponent::textColourId));
repaint();
}
void setInterestedInFileDrag (bool isInterested)
{
interestedInFileDrag = isInterested;
}
void setTextToDisplayWhenEmpty (const String& text, float alpha)
{
textToDisplayWhenEmpty = text;
alphaToUseForEmptyText = alpha;
}
void paintOverChildren (Graphics& g) override
{
if (getText().isEmpty() && ! isBeingEdited())
{
auto& lf = owner.getLookAndFeel();
auto textArea = lf.getLabelBorderSize (*this).subtractedFrom (getLocalBounds());
auto labelFont = lf.getLabelFont (*this);
g.setColour (owner.findColour (TextPropertyComponent::textColourId).withAlpha (alphaToUseForEmptyText));
g.setFont (labelFont);
g.drawFittedText (textToDisplayWhenEmpty, textArea, getJustificationType(),
jmax (1, (int) ((float) textArea.getHeight() / labelFont.getHeight())),
getMinimumHorizontalScale());
}
}
private:
TextPropertyComponent& owner;
int maxChars;
bool isMultiline;
bool interestedInFileDrag = true;
String textToDisplayWhenEmpty;
float alphaToUseForEmptyText = 0.0f;
};
//==============================================================================
class TextPropertyComponent::RemapperValueSourceWithDefault : public Value::ValueSource
{
public:
RemapperValueSourceWithDefault (ValueWithDefault* vwd)
: valueWithDefault (vwd)
{
}
var getValue() const override
{
if (valueWithDefault == nullptr || valueWithDefault->isUsingDefault())
return {};
return valueWithDefault->get();
}
void setValue (const var& newValue) override
{
if (valueWithDefault == nullptr)
return;
if (newValue.toString().isEmpty())
valueWithDefault->resetToDefault();
else
*valueWithDefault = newValue;
}
private:
WeakReference<ValueWithDefault> valueWithDefault;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RemapperValueSourceWithDefault)
};
//==============================================================================
TextPropertyComponent::TextPropertyComponent (const String& name,
int maxNumChars,
bool multiLine,
bool isEditable)
: PropertyComponent (name),
isMultiLine (multiLine)
{
createEditor (maxNumChars, isEditable);
}
TextPropertyComponent::TextPropertyComponent (const Value& valueToControl, const String& name,
int maxNumChars, bool multiLine, bool isEditable)
: TextPropertyComponent (name, maxNumChars, multiLine, isEditable)
{
textEditor->getTextValue().referTo (valueToControl);
}
TextPropertyComponent::TextPropertyComponent (ValueWithDefault& valueToControl, const String& name,
int maxNumChars, bool multiLine, bool isEditable)
: TextPropertyComponent (name, maxNumChars, multiLine, isEditable)
{
valueWithDefault = &valueToControl;
textEditor->getTextValue().referTo (Value (new RemapperValueSourceWithDefault (valueWithDefault)));
textEditor->setTextToDisplayWhenEmpty (valueWithDefault->getDefault(), 0.5f);
valueWithDefault->onDefaultChange = [this]
{
textEditor->setTextToDisplayWhenEmpty (valueWithDefault->getDefault(), 0.5f);
repaint();
};
}
TextPropertyComponent::~TextPropertyComponent()
{
if (valueWithDefault != nullptr)
valueWithDefault->onDefaultChange = nullptr;
}
void TextPropertyComponent::setText (const String& newText)
{
textEditor->setText (newText, sendNotificationSync);
}
String TextPropertyComponent::getText() const
{
return textEditor->getText();
}
Value& TextPropertyComponent::getValue() const
{
return textEditor->getTextValue();
}
void TextPropertyComponent::createEditor (int maxNumChars, bool isEditable)
{
textEditor.reset (new LabelComp (*this, maxNumChars, isMultiLine, isEditable));
addAndMakeVisible (textEditor.get());
if (isMultiLine)
{
textEditor->setJustificationType (Justification::topLeft);
preferredHeight = 100;
}
}
void TextPropertyComponent::refresh()
{
textEditor->setText (getText(), dontSendNotification);
}
void TextPropertyComponent::textWasEdited()
{
auto newText = textEditor->getText();
if (getText() != newText)
setText (newText);
callListeners();
}
void TextPropertyComponent::addListener (TextPropertyComponent::Listener* l) { listenerList.add (l); }
void TextPropertyComponent::removeListener (TextPropertyComponent::Listener* l) { listenerList.remove (l); }
void TextPropertyComponent::callListeners()
{
Component::BailOutChecker checker (this);
listenerList.callChecked (checker, [this] (Listener& l) { l.textPropertyComponentChanged (this); });
}
void TextPropertyComponent::colourChanged()
{
PropertyComponent::colourChanged();
textEditor->updateColours();
}
void TextPropertyComponent::setInterestedInFileDrag (bool isInterested)
{
if (textEditor != nullptr)
textEditor->setInterestedInFileDrag (isInterested);
}
void TextPropertyComponent::setEditable (bool isEditable)
{
if (textEditor != nullptr)
textEditor->setEditable (isEditable, isEditable);
}
} // namespace juce

View File

@ -0,0 +1,191 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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 6 End-User License
Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
End User License Agreement: www.juce.com/juce-6-licence
Privacy Policy: www.juce.com/juce-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 editable text.
@see PropertyComponent
@tags{GUI}
*/
class JUCE_API TextPropertyComponent : public PropertyComponent
{
protected:
//==============================================================================
/** Creates a text property component.
@param propertyName The name of the property
@param maxNumChars If not zero, then this specifies the maximum allowable length of
the string. If zero, then the string will have no length limit.
@param isMultiLine Sets whether the text editor allows carriage returns.
@param isEditable Sets whether the text editor is editable. The default is true.
@see TextEditor, setEditable
*/
TextPropertyComponent (const String& propertyName,
int maxNumChars,
bool isMultiLine,
bool isEditable = true);
public:
/** Creates a text property component.
@param valueToControl The Value that is controlled by the TextPropertyComponent
@param propertyName The name of the property
@param maxNumChars If not zero, then this specifies the maximum allowable length of
the string. If zero, then the string will have no length limit.
@param isMultiLine Sets whether the text editor allows carriage returns.
@param isEditable Sets whether the text editor is editable. The default is true.
@see TextEditor, setEditable
*/
TextPropertyComponent (const Value& valueToControl,
const String& propertyName,
int maxNumChars,
bool isMultiLine,
bool isEditable = true);
/** Creates a text property component with a default value.
@param valueToControl The ValueWithDefault that is controlled by the TextPropertyComponent.
@param propertyName The name of the property
@param maxNumChars If not zero, then this specifies the maximum allowable length of
the string. If zero, then the string will have no length limit.
@param isMultiLine Sets whether the text editor allows carriage returns.
@param isEditable Sets whether the text editor is editable. The default is true.
@see TextEditor, setEditable
*/
TextPropertyComponent (ValueWithDefault& valueToControl,
const String& propertyName,
int maxNumChars,
bool isMultiLine,
bool isEditable = true);
/** Destructor. */
~TextPropertyComponent() override;
//==============================================================================
/** Called when the user edits the text.
Your subclass must use this callback to change the value of whatever item
this property component represents.
*/
virtual void setText (const String& newText);
/** Returns the text that should be shown in the text editor. */
virtual String getText() const;
/** Returns the text that should be shown in the text editor as a Value object. */
Value& getValue() const;
//==============================================================================
/** Returns true if the text editor allows carriage returns. */
bool isTextEditorMultiLine() const noexcept { return isMultiLine; }
//==============================================================================
/** A set of colour IDs to use to change the colour of various aspects of the component.
These constants can be used either via the Component::setColour(), or LookAndFeel::setColour()
methods.
@see Component::setColour, Component::findColour, LookAndFeel::setColour, LookAndFeel::findColour
*/
enum ColourIds
{
backgroundColourId = 0x100e401, /**< The colour to fill the background of the text area. */
textColourId = 0x100e402, /**< The colour to use for the editable text. */
outlineColourId = 0x100e403, /**< The colour to use to draw an outline around the text area. */
};
void colourChanged() override;
//==============================================================================
/** Used to receive callbacks for text changes */
class JUCE_API Listener
{
public:
/** Destructor. */
virtual ~Listener() = default;
/** Called when text has finished being entered (i.e. not per keypress) has changed. */
virtual void textPropertyComponentChanged (TextPropertyComponent*) = 0;
};
/** Registers a listener to receive events when this button's state changes.
If the listener is already registered, this will not register it again.
@see removeListener
*/
void addListener (Listener* newListener);
/** Removes a previously-registered button listener
@see addListener
*/
void removeListener (Listener* listener);
//==============================================================================
/** Sets whether the text property component can have files dropped onto it by an external application.
The default setting for this is true but you may want to disable this behaviour if you derive
from this class and want your subclass to respond to the file drag.
*/
void setInterestedInFileDrag (bool isInterested);
/** Sets whether the text editor is editable. The default setting for this is true. */
void setEditable (bool isEditable);
//==============================================================================
/** @internal */
void refresh() override;
/** @internal */
virtual void textWasEdited();
private:
class RemapperValueSourceWithDefault;
class LabelComp;
friend class LabelComp;
//==============================================================================
void callListeners();
void createEditor (int maxNumChars, bool isEditable);
//==============================================================================
bool isMultiLine;
std::unique_ptr<LabelComp> textEditor;
ListenerList<Listener> listenerList;
WeakReference<ValueWithDefault> valueWithDefault;
//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (TextPropertyComponent)
};
} // namespace juce