Skip to content

Instantly share code, notes, and snippets.

@talaviram
Created September 4, 2017 11:52
Show Gist options
  • Save talaviram/b1542d5da57030320d8acdcdf80a1dad to your computer and use it in GitHub Desktop.
Save talaviram/b1542d5da57030320d8acdcdf80a1dad to your computer and use it in GitHub Desktop.
/*
==============================================================================
ModesButton.cpp
Created: 11 Jul 2017 2:11:07pm
Author: Tal Aviram
==============================================================================
*/
#include "ModesButton.h"
ModesButton::ModesButton() :
ModesButton::ModesButton(ButtonType::SingleButton, 0)
{
}
ModesButton::ModesButton(ButtonType type, int groupId) :
currentIndex_(-1)
,sumOfWeights_(0.0)
,margin_(0)
,isSingleButtonToggled_(false)
{
groupId_ = groupId;
buttonType_ = type;
}
ModesButton::~ModesButton()
{
for (auto btn : modeButtons_)
{
btn->removeListener(this);
delete btn;
}
}
void ModesButton::addListener(Listener* listener)
{
listeners.add (listener);
}
void ModesButton::removeListener(ModesButton::Listener *listener)
{
listeners.remove(listener);
}
void ModesButton::sendChange (const NotificationType notification)
{
if (notification != dontSendNotification)
triggerAsyncUpdate();
if (notification == sendNotificationSync)
handleUpdateNowIfNeeded();
}
void ModesButton::handleAsyncUpdate()
{
cancelPendingUpdate();
Component::BailOutChecker checker (this);
listeners.callChecked (checker, &Listener::modeHasChanged, this);
}
void ModesButton::buttonClicked (Button* btn)
{
if (buttonType_ == ButtonType::SingleButton)
{
int nextIdx = getCurrentModeIndex() + 1;
int idx = (nextIdx % (getNumOfModes()));
setCurrentModeIndex(idx, juce::sendNotificationSync);
return;
}
for (int idx = 0; idx < modeButtons_.size(); ++idx)
{
if (btn == modeButtons_[idx] && btn->getToggleState())
{
setCurrentModeIndex(idx, juce::sendNotificationSync);
}
}
}
void ModesButton::addModeButton (Button* modeButton, int buttonSizeWeight)
{
addAndMakeVisible(modeButton);
modeButtons_.add(modeButton);
if (buttonType_ == ButtonType::SingleButton)
{
modeButton->setClickingTogglesState(isSingleButtonToggled_);
modeButton->setToggleState(true, dontSendNotification);
}
else
{
modeButton->setRadioGroupId(groupId_);
modeButton->setClickingTogglesState(true);
modeButton->setToggleState(false, dontSendNotification);
}
sumOfWeights_ += buttonSizeWeight;
modeButtonsWeight_.add(buttonSizeWeight);
modeButton->addListener(this);
setCurrentModeIndex(modeButtons_.size() - 1);
resized();
}
Button* ModesButton::getButtonForMode (int modeIndex) const noexcept
{
return modeButtons_[modeIndex];
}
void ModesButton::setCurrentModeIndex (int newModeIndex, juce::NotificationType n)
{
currentIndex_ = newModeIndex;
if (buttonType_ == ButtonType::SingleButton && newModeIndex == -1)
{
// hide all buttons? this would make the UI unavailable.
jassertfalse;
}
if (buttonType_ == ButtonType::SingleButton)
{
bool didFindValue = false;
for (int i = 0; i < modeButtons_.size(); ++i)
{
if (i == currentIndex_)
{
modeButtons_[i]->setVisible(true);
didFindValue = true;
}
else
modeButtons_[i]->setVisible(false);
}
jassert(didFindValue);
}
else
{
for (int i = 0; i < modeButtons_.size(); ++i)
{
if (i == currentIndex_)
{
modeButtons_[i]->setToggleState(true, juce::dontSendNotification);
}
else
{
modeButtons_[i]->setToggleState(false, juce::dontSendNotification);
}
}
}
sendChange(n);
}
int ModesButton::getNumOfModes() const
{
return modeButtons_.size();
}
void ModesButton::resized()
{
int modeIndex = 0;
int startPoint = 0;
for (auto btn : modeButtons_) {
if (!btn->isVisible())
{
if (buttonType_ == ButtonType::SingleButton)
{
if (modeIndex == currentIndex_) addAndMakeVisible(btn);
}
else
{
addAndMakeVisible(btn);
}
}
if (buttonType_ == ButtonType::SingleButton)
btn->setBounds(0, 0, getWidth(), getHeight());
if (buttonType_ == ButtonType::HorizontalButtons)
{
int btnWidth = roundToInt((1.0 * modeButtonsWeight_[modeIndex] / sumOfWeights_) * (getWidth() - margin_ * getNumOfModes() * 2));
btn->setBounds(margin_ + startPoint, margin_, btnWidth, getHeight() - margin_);
startPoint += btnWidth + margin_;
}
if (buttonType_ == ButtonType::VerticalButtons)
{
int btnHeight = roundToInt((1.0 * modeButtonsWeight_[modeIndex] / sumOfWeights_) * (getHeight() - margin_ * getNumOfModes() * 2));
btn->setBounds(margin_, startPoint + margin_, getWidth(), btnHeight - margin_);
startPoint += btnHeight + margin_;
}
modeIndex++;
}
}
ModesButton::ModesButtonAttachment::ModesButtonAttachment (AudioProcessorValueTreeState& s, const String& p, ModesButton& mb) :
paramID(p), params(s), modesButton(mb), lastValue (0), ignoreCallbacks(false)
{
sendInitialUpdate();
modesButton.addListener (this);
params.addParameterListener(paramID, this);
}
ModesButton::ModesButtonAttachment::~ModesButtonAttachment()
{
modesButton.removeListener (this);
params.removeParameterListener(paramID, this);
}
void ModesButton::ModesButtonAttachment::sendInitialUpdate()
{
if (float* v = params.getRawParameterValue (paramID))
parameterChanged (paramID, *v);
}
void ModesButton::ModesButtonAttachment::parameterChanged (const String& s, float newValue)
{
lastValue = newValue;
if (MessageManager::getInstance()->isThisTheMessageThread())
{
cancelPendingUpdate();
setValue (newValue);
}
else
{
triggerAsyncUpdate();
}
}
void ModesButton::ModesButtonAttachment::setValue (float newValue)
{
const ScopedLock selfCallbackLock (selfCallbackMutex);
{
ScopedValueSetter<bool> svs (ignoreCallbacks, true);
modesButton.setCurrentModeIndex (roundToInt (newValue), sendNotificationSync);
}
}
void ModesButton::ModesButtonAttachment::modeHasChanged (ModesButton* btn)
{
const int newIndex = btn->getCurrentModeIndex();
const ScopedLock selfCallbackLock (selfCallbackMutex);
if (! ignoreCallbacks)
{
beginParameterChange();
setNewUnnormalisedValue ((float) newIndex);
endParameterChange();
}
}
void ModesButton::ModesButtonAttachment::setNewUnnormalisedValue (float newUnnormalisedValue)
{
if (AudioProcessorParameter* p = params.getParameter (paramID))
{
const float newValue = params.getParameterRange (paramID)
.convertTo0to1 (newUnnormalisedValue);
if (p->getValue() != newValue)
p->setValueNotifyingHost (newValue);
}
}
void ModesButton::ModesButtonAttachment::beginParameterChange()
{
if (AudioProcessorParameter* p = params.getParameter (paramID))
p->beginChangeGesture();
}
void ModesButton::ModesButtonAttachment::endParameterChange()
{
if (AudioProcessorParameter* p = params.getParameter (paramID))
p->endChangeGesture();
}
void ModesButton::ModesButtonAttachment::handleAsyncUpdate()
{
setValue (lastValue);
}
/*
==============================================================================
ModesButton.h -
This is actually multiple button grouped to allow multiple switching.
It also includes attachment for AudioProcessValueTree.
It is able to be a row of buttons (modes) or a single area to switch modes/buttons.
Created: 11 Jul 2017 2:11:07pm
Author: Tal Aviram
==============================================================================
*/
#pragma once
#include "JuceHeader.h"
class ModesButton : public juce::Component,
juce::Button::Listener,
private AsyncUpdater
{
public:
class Listener
{
public:
/** Destructor. */
virtual ~Listener() {}
/** Called when a ModesButton mode/index changed. */
virtual void modeHasChanged (ModesButton* btn) = 0;
};
class ModesButtonAttachment : public juce::AsyncUpdater,
public juce::AudioProcessorValueTreeState::Listener,
private ModesButton::Listener
{
public:
ModesButtonAttachment (AudioProcessorValueTreeState& s, const String& p, ModesButton& mb);
~ModesButtonAttachment();
void modeHasChanged (ModesButton* btn) override;
void handleAsyncUpdate() override;
private:
void sendInitialUpdate();
void setValue (float newValue);
void parameterChanged (const String&, float newValue) override;
void beginParameterChange();
void endParameterChange();
void setNewUnnormalisedValue (float newUnnormalisedValue);
String paramID;
AudioProcessorValueTreeState& params;
ModesButton& modesButton;
float lastValue;
bool ignoreCallbacks;
CriticalSection selfCallbackMutex;
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ModesButtonAttachment)
};
enum ButtonType { SingleButton, HorizontalButtons, VerticalButtons };
ModesButton();
ModesButton(ButtonType type, int groupId);
~ModesButton();
/** Adds a button to as a mode.
The button passed in will be shown for that mode.
ModesButon will take ownership of the component and will delete
it when the button is removed or when this object is deleted.
@see ModesButton::addModeButton
*/
void addModeButton (Button* modeButton, int buttonSizeWeight = 1);
/** Returns the number of buttons/modes in the bar. */
int getNumOfModes() const;
/** Returns the button that was added for the given index.
Be careful not to delete the buttons that are returned, as
this will interfere with the ModesButton behaviour.
*/
Button* getButtonForMode (int modeIndex) const noexcept;
//==============================================================================
/** Changes the currently-selected mode.
To deselect all the buttons, pass -1 as the index.
*/
void setCurrentModeIndex (int newTabIndex, juce::NotificationType n = juce::dontSendNotification);
/** Useful for adding space between the buttons. */
void setMarginBetweenButtons(int marginInPixels) { margin_ = marginInPixels; }
void setSingleButtonIsToggled(bool isSingleToggled) { isSingleButtonToggled_ = isSingleToggled; }
bool isSingleButtonToggled() { return isSingleButtonToggled_; }
/** Returns the index of the currently selected mode.
@see addModeButton, ModesButton::getCurrentModeIndex()
*/
int getCurrentModeIndex() const { return currentIndex_; }
//==============================================================================
void buttonClicked (Button*) override;
void resized() override;
//==============================================================================
void addListener(Listener* listener);
void removeListener(Listener* listener);
void handleAsyncUpdate() override;
private:
//==============================================================================
int currentIndex_;
int groupId_;
int sumOfWeights_;
int margin_;
ButtonType buttonType_;
bool isSingleButtonToggled_;
Array <Button*> modeButtons_;
Array<int> modeButtonsWeight_;
juce::ListenerList<Listener> listeners;
void sendChange (const NotificationType notification);
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ModesButton)
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment