#pragma once #include #include "../audio/BooleanParameter.h" class SvgButton : public juce::DrawableButton, public juce::AudioProcessorParameter::Listener, public juce::AsyncUpdater { public: SvgButton(juce::String name, juce::String svg, juce::Colour colour, juce::Colour colourOn, BooleanParameter* toggle = nullptr) : juce::DrawableButton(name, juce::DrawableButton::ButtonStyle::ImageFitted), toggle(toggle) { auto doc = juce::XmlDocument::parse(svg); changeSvgColour(doc.get(), colour); normalImage = juce::Drawable::createFromSVG(*doc); changeSvgColour(doc.get(), colour.withBrightness(0.7f)); overImage = juce::Drawable::createFromSVG(*doc); changeSvgColour(doc.get(), colour.withBrightness(0.5f)); downImage = juce::Drawable::createFromSVG(*doc); changeSvgColour(doc.get(), colour.withBrightness(0.3f)); disabledImage = juce::Drawable::createFromSVG(*doc); changeSvgColour(doc.get(), colourOn); normalImageOn = juce::Drawable::createFromSVG(*doc); changeSvgColour(doc.get(), colourOn.withBrightness(0.7f)); overImageOn = juce::Drawable::createFromSVG(*doc); changeSvgColour(doc.get(), colourOn.withBrightness(0.5f)); downImageOn = juce::Drawable::createFromSVG(*doc); changeSvgColour(doc.get(), colourOn.withBrightness(0.3f)); disabledImageOn = juce::Drawable::createFromSVG(*doc); basePath = normalImage->getOutlineAsPath(); getLookAndFeel().setColour(juce::DrawableButton::backgroundOnColourId, juce::Colours::transparentWhite); if (colour != colourOn) { setClickingTogglesState(true); } setImages(normalImage.get(), overImage.get(), downImage.get(), disabledImage.get(), normalImageOn.get(), overImageOn.get(), downImageOn.get(), disabledImageOn.get()); if (toggle != nullptr) { toggle->addListener(this); setToggleState(toggle->getBoolValue(), juce::NotificationType::dontSendNotification); setTooltip(toggle->getDescription()); } updater.addAnimator(pulse); } SvgButton(juce::String name, juce::String svg, juce::Colour colour) : SvgButton(name, svg, colour, colour) {} ~SvgButton() override { if (toggle != nullptr) { toggle->removeListener(this); } } void parameterValueChanged(int parameterIndex, float newValue) override { triggerAsyncUpdate(); } void parameterGestureChanged(int parameterIndex, bool gestureIsStarting) override {} void handleAsyncUpdate() override { setToggleState(toggle->getBoolValue(), juce::NotificationType::dontSendNotification); } void mouseEnter(const juce::MouseEvent& e) override { juce::DrawableButton::mouseEnter(e); if (isEnabled()) { setMouseCursor(juce::MouseCursor::PointingHandCursor); } } void mouseExit(const juce::MouseEvent& e) override { juce::DrawableButton::mouseExit(e); setMouseCursor(juce::MouseCursor::NormalCursor); } void setPulseAnimation(bool pulseUsed) { this->pulseUsed = pulseUsed; } void resized() override { juce::DrawableButton::resized(); if (pulseUsed) { resizedPath = basePath; // scale path to fit image resizedPath.applyTransform(resizedPath.getTransformToScaleToFit(getImageBounds(), true)); repaint(); } } void paintOverChildren(juce::Graphics& g) override { if (pulseUsed && getToggleState()) { g.setColour(juce::Colours::black.withAlpha(colourFade / 1.5f)); g.fillPath(resizedPath); } } void buttonStateChanged() override { juce::DrawableButton::buttonStateChanged(); if (pulseUsed && getToggleState() != prevToggleState) { if (getToggleState()) { pulse.start(); } else { pulse.complete(); colourFade = 1.0; } prevToggleState = getToggleState(); } } private: std::unique_ptr normalImage; std::unique_ptr overImage; std::unique_ptr downImage; std::unique_ptr disabledImage; std::unique_ptr normalImageOn; std::unique_ptr overImageOn; std::unique_ptr downImageOn; std::unique_ptr disabledImageOn; BooleanParameter* toggle; juce::VBlankAnimatorUpdater updater{this}; float colourFade = 0.0; bool pulseUsed = false; bool prevToggleState = false; juce::Path basePath; juce::Path resizedPath; juce::Animator pulse = juce::ValueAnimatorBuilder {} .withEasing([] (float t) { return std::sin(3.14159 * t) / 2 + 0.5; }) .withDurationMs(500) .runningInfinitely() .withValueChangedCallback([this] (auto value) { colourFade = value; repaint(); }) .build(); void changeSvgColour(juce::XmlElement* xml, juce::Colour colour) { forEachXmlChildElement(*xml, xmlnode) { xmlnode->setAttribute("fill", '#' + colour.toDisplayString(false)); } } };