ModulatableSlider.cpp (13382B)
1 #include "ModulatableSlider.h" 2 #include "../PluginProcessor.h" 3 4 void ModulatableSlider::attachToParameter (juce::RangedAudioParameter* param) 5 { 6 if (param == nullptr) 7 { 8 attachment.reset(); 9 modParameter = nullptr; 10 stopTimer(); 11 return; 12 } 13 14 attachment = std::make_unique<juce::SliderParameterAttachment> (*param, *this, nullptr); 15 modParameter = dynamic_cast<chowdsp::FloatParameter*> (param); 16 modulatedValue = modParameter->getCurrentValue(); 17 } 18 19 double ModulatableSlider::getModulatedPosition() 20 { 21 if (modParameter == nullptr) 22 return valueToProportionOfLength (getValue()); 23 24 return jlimit (0.0, 1.0, valueToProportionOfLength ((double) modParameter->getCurrentValue())); 25 } 26 27 void ModulatableSlider::mouseDown (const MouseEvent& e) 28 { 29 if (e.mods.isPopupMenu()) 30 { 31 auto popupMenu = getContextMenu(); 32 if (popupMenu.containsAnyActiveItems()) 33 popupMenu.showMenuAsync (juce::PopupMenu::Options()); 34 35 return; 36 } 37 38 foleys::AutoOrientationSlider::mouseDown (e); 39 } 40 41 void ModulatableSlider::setPluginEditorCallback (PluginEditorCallback&& newCallback) 42 { 43 pluginEditorCallback = std::move (newCallback); 44 } 45 46 juce::PopupMenu ModulatableSlider::getContextMenu() 47 { 48 if (pluginEditorCallback != nullptr) 49 { 50 if (auto* pluginEditor = pluginEditorCallback()) 51 { 52 if (auto* hostContext = pluginEditor->getHostContext()) 53 { 54 if (auto menu = hostContext->getContextMenuForParameter (modParameter)) 55 return menu->getEquivalentPopupMenu(); 56 } 57 } 58 } 59 60 return {}; 61 } 62 63 void ModulatableSlider::timerCallback() 64 { 65 const auto newModulatedValue = modParameter->getCurrentValue(); 66 if (std::abs (modulatedValue - newModulatedValue) < 0.01) 67 return; 68 69 modulatedValue = newModulatedValue; 70 repaint(); 71 } 72 73 void ModulatableSlider::drawRotarySlider (juce::Graphics& g, int x, int y, int width, int height, float sliderPos, float modSliderPos) 74 { 75 int diameter = (width > height) ? height : width; 76 if (diameter < 16) 77 return; 78 79 juce::Point<float> centre ((float) x + std::floor ((float) width * 0.5f + 0.5f), (float) y + std::floor ((float) height * 0.5f + 0.5f)); 80 diameter -= (diameter % 2 == 1) ? 9 : 8; 81 float radius = (float) diameter * 0.5f; 82 x = int (centre.x - radius); 83 y = int (centre.y - radius); 84 85 const auto bounds = juce::Rectangle<int> (x, y, diameter, diameter).toFloat(); 86 87 auto b = sharedAssets->pointer->getBounds().toFloat(); 88 sharedAssets->pointer->setTransform (AffineTransform::rotation (MathConstants<float>::twoPi * ((sliderPos - 0.5f) * 300.0f / 360.0f), 89 b.getCentreX(), 90 b.getCentreY())); 91 92 const auto alpha = isEnabled() ? 1.0f : 0.4f; 93 auto knobBounds = (bounds * 0.75f).withCentre (centre); 94 sharedAssets->knob->drawWithin (g, knobBounds, RectanglePlacement::stretchToFit, alpha); 95 sharedAssets->pointer->drawWithin (g, knobBounds, RectanglePlacement::stretchToFit, alpha); 96 97 static constexpr auto rotaryStartAngle = MathConstants<float>::pi * 1.2f; 98 static constexpr auto rotaryEndAngle = MathConstants<float>::pi * 2.8f; 99 const auto toAngle = rotaryStartAngle + modSliderPos * (rotaryEndAngle - rotaryStartAngle); 100 constexpr float arcFactor = 0.9f; 101 102 juce::Path valueArc; 103 valueArc.addPieSegment (bounds, rotaryStartAngle, rotaryEndAngle, arcFactor); 104 g.setColour (Colour (0xff595c6b).withMultipliedAlpha (alpha)); 105 g.fillPath (valueArc); 106 valueArc.clear(); 107 108 valueArc.addPieSegment (bounds, rotaryStartAngle, toAngle, arcFactor); 109 g.setColour (Colour (0xff9cbcbd).withMultipliedAlpha (alpha)); 110 g.fillPath (valueArc); 111 } 112 113 void ModulatableSlider::drawLinearSlider (juce::Graphics& g, int x, int y, int width, int height, float sliderPos, float modSliderPos) 114 { 115 const auto horizontal = isHorizontal(); 116 auto trackWidth = juce::jmin (6.0f, horizontal ? (float) height * 0.25f : (float) width * 0.25f); 117 118 juce::Point startPoint (horizontal ? (float) x : (float) x + (float) width * 0.5f, 119 horizontal ? (float) y + (float) height * 0.5f : (float) (height + y)); 120 121 juce::Point endPoint (horizontal ? (float) (width + x) : startPoint.x, 122 horizontal ? startPoint.y : (float) y); 123 124 juce::Path backgroundTrack; 125 backgroundTrack.startNewSubPath (startPoint); 126 backgroundTrack.lineTo (endPoint); 127 128 const auto alphaMult = isEnabled() ? 1.0f : 0.4f; 129 g.setColour (Colour (0xff595c6b).withAlpha (alphaMult)); 130 g.strokePath (backgroundTrack, { trackWidth, juce::PathStrokeType::curved, juce::PathStrokeType::rounded }); 131 132 juce::Path valueTrack; 133 const auto maxPoint = [&] 134 { 135 auto kx = horizontal ? sliderPos : ((float) x + (float) width * 0.5f); 136 auto ky = horizontal ? ((float) y + (float) height * 0.5f) : sliderPos; 137 return juce::Point { kx, ky }; 138 }(); 139 const auto modPoint = [&] 140 { 141 auto kmx = horizontal ? modSliderPos : ((float) x + (float) width * 0.5f); 142 auto kmy = horizontal ? ((float) y + (float) height * 0.5f) : modSliderPos; 143 return juce::Point { kmx, kmy }; 144 }(); 145 146 valueTrack.startNewSubPath (startPoint); 147 valueTrack.lineTo (modPoint); 148 g.setColour (Colour (0xff9cbcbd).withAlpha (alphaMult)); 149 g.strokePath (valueTrack, { trackWidth, juce::PathStrokeType::curved, juce::PathStrokeType::rounded }); 150 151 auto thumbWidth = getLookAndFeel().getSliderThumbRadius (*this); 152 auto thumbRect = juce::Rectangle<float> (static_cast<float> (thumbWidth), 153 static_cast<float> (thumbWidth)) 154 .withCentre (maxPoint); 155 sharedAssets->knob->drawWithin (g, thumbRect, juce::RectanglePlacement::stretchToFit, alphaMult); 156 } 157 158 void ModulatableSlider::paint (Graphics& g) 159 { 160 if (modParameter == nullptr) 161 return; 162 163 auto& lf = getLookAndFeel(); 164 auto layout = lf.getSliderLayout (*this); 165 const auto sliderRect = layout.sliderBounds; 166 167 modulatedValue = modParameter->getCurrentValue(); 168 if (isRotary()) 169 { 170 const auto sliderPos = (float) valueToProportionOfLength (getValue()); 171 const auto modSliderPos = (float) jlimit (0.0, 1.0, valueToProportionOfLength (modulatedValue)); 172 drawRotarySlider (g, 173 sliderRect.getX(), 174 sliderRect.getY(), 175 sliderRect.getWidth(), 176 sliderRect.getHeight(), 177 sliderPos, 178 modSliderPos); 179 } 180 else 181 { 182 const auto normRange = NormalisableRange { getRange() }; 183 const auto sliderPos = getPositionOfValue (getValue()); 184 const auto modSliderPos = getPositionOfValue (modulatedValue); 185 drawLinearSlider (g, 186 sliderRect.getX(), 187 sliderRect.getY(), 188 sliderRect.getWidth(), 189 sliderRect.getHeight(), 190 sliderPos, 191 modSliderPos); 192 } 193 } 194 195 //==================================================================== 196 ModSliderItem::ModSliderItem (foleys::MagicGUIBuilder& builder, const juce::ValueTree& node) : GuiItem (builder, node) 197 { 198 setColourTranslation ( 199 { { "slider-background", juce::Slider::backgroundColourId }, 200 { "slider-thumb", juce::Slider::thumbColourId }, 201 { "slider-track", juce::Slider::trackColourId }, 202 { "rotary-fill", juce::Slider::rotarySliderFillColourId }, 203 { "rotary-outline", juce::Slider::rotarySliderOutlineColourId }, 204 { "slider-text", juce::Slider::textBoxTextColourId }, 205 { "slider-text-background", juce::Slider::textBoxBackgroundColourId }, 206 { "slider-text-highlight", juce::Slider::textBoxHighlightColourId }, 207 { "slider-text-outline", juce::Slider::textBoxOutlineColourId } }); 208 209 addAndMakeVisible (slider); 210 } 211 212 void ModSliderItem::update() 213 { 214 if (const auto* plugin = dynamic_cast<const ChowtapeModelAudioProcessor*> (magicBuilder.getMagicState().getProcessor())) 215 { 216 if (plugin->supportsParameterModulation()) 217 slider.startTimerHz (24); 218 } 219 220 slider.setPluginEditorCallback ([this] 221 { return magicBuilder.getMagicState().getProcessor()->getActiveEditor(); }); 222 223 slider.setTitle (magicBuilder.getStyleProperty (foleys::IDs::name, configNode)); 224 defaultHeight = magicBuilder.getStyleProperty (foleys::IDs::defaultHeight, configNode); 225 226 auto type = getProperty (pSliderType).toString(); 227 slider.setAutoOrientation (type.isEmpty() || type == pSliderTypes[0]); 228 229 if (type == pSliderTypes[1]) 230 slider.setSliderStyle (juce::Slider::LinearHorizontal); 231 else if (type == pSliderTypes[2]) 232 slider.setSliderStyle (juce::Slider::LinearVertical); 233 else if (type == pSliderTypes[3]) 234 slider.setSliderStyle (juce::Slider::Rotary); 235 else if (type == pSliderTypes[4]) 236 slider.setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag); 237 else if (type == pSliderTypes[5]) 238 slider.setSliderStyle (juce::Slider::IncDecButtons); 239 240 auto textbox = getProperty (pSliderTextBox).toString(); 241 sliderTextBoxHeight = getProperty (pSliderTextBoxHeight); 242 if (textbox == pTextBoxPositions[0]) 243 textBoxPosition = juce::Slider::NoTextBox; 244 else if (textbox == pTextBoxPositions[1]) 245 textBoxPosition = juce::Slider::TextBoxAbove; 246 else if (textbox == pTextBoxPositions[3]) 247 textBoxPosition = juce::Slider::TextBoxLeft; 248 else if (textbox == pTextBoxPositions[4]) 249 textBoxPosition = juce::Slider::TextBoxRight; 250 else 251 textBoxPosition = juce::Slider::TextBoxBelow; 252 253 double minValue = getProperty (pMinValue); 254 double maxValue = getProperty (pMaxValue); 255 if (maxValue > minValue) 256 slider.setRange (minValue, maxValue); 257 258 auto valueID = configNode.getProperty (pValue, juce::String()).toString(); 259 if (valueID.isNotEmpty()) 260 slider.getValueObject().referTo (getMagicState().getPropertyAsValue (valueID)); 261 262 auto paramID = getControlledParameterID ({}); 263 if (paramID.isNotEmpty()) 264 slider.attachToParameter (getMagicState().getParameter (paramID)); 265 else 266 slider.attachToParameter (nullptr); 267 268 resized(); 269 } 270 271 void ModSliderItem::resized() 272 { 273 const auto sliderTextHeightToUse = [this] 274 { 275 if (defaultHeight == 0) 276 return sliderTextBoxHeight; 277 278 return proportionOfHeight ((float) sliderTextBoxHeight / (float) defaultHeight); 279 }(); 280 281 slider.setTextBoxStyle (textBoxPosition, false, proportionOfWidth (0.75f), sliderTextHeightToUse); 282 283 foleys::GuiItem::resized(); 284 } 285 286 std::vector<foleys::SettableProperty> ModSliderItem::getSettableProperties() const 287 { 288 std::vector<foleys::SettableProperty> props; 289 290 props.push_back ({ configNode, foleys::IDs::parameter, foleys::SettableProperty::Choice, {}, magicBuilder.createParameterMenuLambda() }); 291 props.push_back ({ configNode, pSliderType, foleys::SettableProperty::Choice, pSliderTypes[0], magicBuilder.createChoicesMenuLambda (pSliderTypes) }); 292 props.push_back ({ configNode, pSliderTextBox, foleys::SettableProperty::Choice, pTextBoxPositions[2], magicBuilder.createChoicesMenuLambda (pTextBoxPositions) }); 293 props.push_back ({ configNode, pValue, foleys::SettableProperty::Choice, 1.0f, magicBuilder.createPropertiesMenuLambda() }); 294 props.push_back ({ configNode, pMinValue, foleys::SettableProperty::Number, 0.0f, {} }); 295 props.push_back ({ configNode, pMaxValue, foleys::SettableProperty::Number, 2.0f, {} }); 296 props.push_back ({ configNode, pSliderTextBoxWidth, foleys::SettableProperty::Number, 85.0f, {} }); 297 props.push_back ({ configNode, pSliderTextBoxHeight, foleys::SettableProperty::Number, 17.0f, {} }); 298 299 return props; 300 } 301 302 juce::String ModSliderItem::getControlledParameterID (juce::Point<int>) 303 { 304 return configNode.getProperty (foleys::IDs::parameter, juce::String()).toString(); 305 } 306 307 juce::Component* ModSliderItem::getWrappedComponent() 308 { 309 return &slider; 310 } 311 312 void ModSliderItem::mouseDrag (const juce::MouseEvent& e) 313 { 314 auto numSources = juce::Desktop::getInstance().getNumDraggingMouseSources(); 315 if (numSources > 1) 316 return; 317 318 GuiItem::mouseDrag (e); 319 } 320 321 const juce::Identifier ModSliderItem::pSliderType { "slider-type" }; 322 const juce::StringArray ModSliderItem::pSliderTypes { "auto", "linear-horizontal", "linear-vertical", "rotary", "rotary-horizontal-vertical", "inc-dec-buttons" }; 323 const juce::Identifier ModSliderItem::pSliderTextBox { "slider-textbox" }; 324 const juce::StringArray ModSliderItem::pTextBoxPositions { "no-textbox", "textbox-above", "textbox-below", "textbox-left", "textbox-right" }; 325 const juce::Identifier ModSliderItem::pSliderTextBoxWidth { "slidertext-width" }; 326 const juce::Identifier ModSliderItem::pSliderTextBoxHeight { "slidertext-height" }; 327 const juce::Identifier ModSliderItem::pValue { "value" }; 328 const juce::Identifier ModSliderItem::pMinValue { "min-value" }; 329 const juce::Identifier ModSliderItem::pMaxValue { "max-value" };