|  | /*
  ==============================================================================
   This file is part of the JUCE library.
   Copyright (c) 2022 - 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 7 End-User License
   Agreement and JUCE Privacy Policy.
   End User License Agreement: www.juce.com/juce-7-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
{
ParameterAttachment::ParameterAttachment (RangedAudioParameter& param,
                                          std::function<void (float)> parameterChangedCallback,
                                          UndoManager* um)
    : parameter (param),
      undoManager (um),
      setValue (std::move (parameterChangedCallback))
{
    parameter.addListener (this);
}
ParameterAttachment::~ParameterAttachment()
{
    parameter.removeListener (this);
    cancelPendingUpdate();
}
void ParameterAttachment::sendInitialUpdate()
{
    parameterValueChanged ({}, parameter.getValue());
}
void ParameterAttachment::setValueAsCompleteGesture (float newDenormalisedValue)
{
    callIfParameterValueChanged (newDenormalisedValue, [this] (float f)
    {
        beginGesture();
        parameter.setValueNotifyingHost (f);
        endGesture();
    });
}
void ParameterAttachment::beginGesture()
{
    if (undoManager != nullptr)
        undoManager->beginNewTransaction();
    parameter.beginChangeGesture();
}
void ParameterAttachment::setValueAsPartOfGesture (float newDenormalisedValue)
{
    callIfParameterValueChanged (newDenormalisedValue, [this] (float f)
    {
        parameter.setValueNotifyingHost (f);
    });
}
void ParameterAttachment::endGesture()
{
    parameter.endChangeGesture();
}
template <typename Callback>
void ParameterAttachment::callIfParameterValueChanged (float newDenormalisedValue,
                                                       Callback&& callback)
{
    const auto newValue = normalise (newDenormalisedValue);
    if (! approximatelyEqual (parameter.getValue(), newValue))
        callback (newValue);
}
void ParameterAttachment::parameterValueChanged (int, float newValue)
{
    lastValue = newValue;
    if (MessageManager::getInstance()->isThisTheMessageThread())
    {
        cancelPendingUpdate();
        handleAsyncUpdate();
    }
    else
    {
        triggerAsyncUpdate();
    }
}
void ParameterAttachment::handleAsyncUpdate()
{
    if (setValue != nullptr)
        setValue (parameter.convertFrom0to1 (lastValue));
}
//==============================================================================
SliderParameterAttachment::SliderParameterAttachment (RangedAudioParameter& param,
                                                      Slider& s,
                                                      UndoManager* um)
    : slider (s),
      attachment (param, [this] (float f) { setValue (f); }, um)
{
    slider.valueFromTextFunction = [¶m] (const String& text) { return (double) param.convertFrom0to1 (param.getValueForText (text)); };
    slider.textFromValueFunction = [¶m] (double value) { return param.getText (param.convertTo0to1 ((float) value), 0); };
    slider.setDoubleClickReturnValue (true, param.convertFrom0to1 (param.getDefaultValue()));
    auto range = param.getNormalisableRange();
    auto convertFrom0To1Function = [range] (double currentRangeStart,
                                            double currentRangeEnd,
                                            double normalisedValue) mutable
    {
        range.start = (float) currentRangeStart;
        range.end = (float) currentRangeEnd;
        return (double) range.convertFrom0to1 ((float) normalisedValue);
    };
    auto convertTo0To1Function = [range] (double currentRangeStart,
                                          double currentRangeEnd,
                                          double mappedValue) mutable
    {
        range.start = (float) currentRangeStart;
        range.end = (float) currentRangeEnd;
        return (double) range.convertTo0to1 ((float) mappedValue);
    };
    auto snapToLegalValueFunction = [range] (double currentRangeStart,
                                             double currentRangeEnd,
                                             double mappedValue) mutable
    {
        range.start = (float) currentRangeStart;
        range.end = (float) currentRangeEnd;
        return (double) range.snapToLegalValue ((float) mappedValue);
    };
    NormalisableRange<double> newRange { (double) range.start,
                                         (double) range.end,
                                         std::move (convertFrom0To1Function),
                                         std::move (convertTo0To1Function),
                                         std::move (snapToLegalValueFunction) };
    newRange.interval = range.interval;
    newRange.skew = range.skew;
    newRange.symmetricSkew = range.symmetricSkew;
    slider.setNormalisableRange (newRange);
    sendInitialUpdate();
    slider.valueChanged();
    slider.addListener (this);
}
SliderParameterAttachment::~SliderParameterAttachment()
{
    slider.removeListener (this);
}
void SliderParameterAttachment::sendInitialUpdate() { attachment.sendInitialUpdate(); }
void SliderParameterAttachment::setValue (float newValue)
{
    const ScopedValueSetter<bool> svs (ignoreCallbacks, true);
    slider.setValue (newValue, sendNotificationSync);
}
void SliderParameterAttachment::sliderValueChanged (Slider*)
{
    if (! ignoreCallbacks)
        attachment.setValueAsPartOfGesture ((float) slider.getValue());
}
//==============================================================================
ComboBoxParameterAttachment::ComboBoxParameterAttachment (RangedAudioParameter& param,
                                                          ComboBox& c,
                                                          UndoManager* um)
    : comboBox (c),
      storedParameter (param),
      attachment (param, [this] (float f) { setValue (f); }, um)
{
    sendInitialUpdate();
    comboBox.addListener (this);
}
ComboBoxParameterAttachment::~ComboBoxParameterAttachment()
{
    comboBox.removeListener (this);
}
void ComboBoxParameterAttachment::sendInitialUpdate()
{
    attachment.sendInitialUpdate();
}
void ComboBoxParameterAttachment::setValue (float newValue)
{
    const auto normValue = storedParameter.convertTo0to1 (newValue);
    const auto index = roundToInt (normValue * (float) (comboBox.getNumItems() - 1));
    if (index == comboBox.getSelectedItemIndex())
        return;
    const ScopedValueSetter<bool> svs (ignoreCallbacks, true);
    comboBox.setSelectedItemIndex (index, sendNotificationSync);
}
void ComboBoxParameterAttachment::comboBoxChanged (ComboBox*)
{
    if (ignoreCallbacks)
        return;
    const auto numItems = comboBox.getNumItems();
    const auto selected = (float) comboBox.getSelectedItemIndex();
    const auto newValue = numItems > 1 ? selected / (float) (numItems - 1)
                                       : 0.0f;
    attachment.setValueAsCompleteGesture (storedParameter.convertFrom0to1 (newValue));
}
//==============================================================================
ButtonParameterAttachment::ButtonParameterAttachment (RangedAudioParameter& param,
                                                      Button& b,
                                                      UndoManager* um)
    : button (b),
      attachment (param, [this] (float f) { setValue (f); }, um)
{
    sendInitialUpdate();
    button.addListener (this);
}
ButtonParameterAttachment::~ButtonParameterAttachment()
{
    button.removeListener (this);
}
void ButtonParameterAttachment::sendInitialUpdate()
{
    attachment.sendInitialUpdate();
}
void ButtonParameterAttachment::setValue (float newValue)
{
    const ScopedValueSetter<bool> svs (ignoreCallbacks, true);
    button.setToggleState (newValue >= 0.5f, sendNotificationSync);
}
void ButtonParameterAttachment::buttonClicked (Button*)
{
    if (ignoreCallbacks)
        return;
    attachment.setValueAsCompleteGesture (button.getToggleState() ? 1.0f : 0.0f);
}
} // namespace juce
 |