├── .gitignore ├── Assets ├── Background.png ├── RasterKnob.png └── VectorKnob.svg ├── README.md ├── ResizableApp.jucer └── Source ├── Components ├── RasterKnob.h └── VectorKnob.h ├── PluginProcessor.cpp ├── PluginProcessor.h ├── RasterEditor.cpp ├── RasterEditor.h ├── VectorEditor.cpp └── VectorEditor.h /.gitignore: -------------------------------------------------------------------------------- 1 | **/Builds/ 2 | *.exe 3 | *.pkg 4 | 5 | JuceLibraryCode 6 | -------------------------------------------------------------------------------- /Assets/Background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thrifleganger/resizable-plugin-window-demo/a59779cf5323d918ed7de985028f36c14273eaad/Assets/Background.png -------------------------------------------------------------------------------- /Assets/RasterKnob.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Thrifleganger/resizable-plugin-window-demo/a59779cf5323d918ed7de985028f36c14273eaad/Assets/RasterKnob.png -------------------------------------------------------------------------------- /Assets/VectorKnob.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Resizable plugin window 2 | 3 | This is a small demo project on modifying an existing plugin project made up of raster images and fixed sized positioning and making it scalable and resizable. 4 | 5 | The demo is featured in the following YouTube video: https://www.youtube.com/watch?v=0hh1-D3gLKw 6 | 7 | ### Requirements 8 | 9 | 1. [JUCE v7.0.0](https://juce.com/get-juce/download) 10 | 2. C++17 compiler 11 | 3. IDE of your choice (Visual Studio, Xcode, etc) -------------------------------------------------------------------------------- /ResizableApp.jucer: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 18 | 20 | 22 | 23 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /Source/Components/RasterKnob.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class RasterKnob : public Slider 6 | { 7 | public: 8 | RasterKnob() : Slider(SliderStyle::RotaryHorizontalVerticalDrag, TextEntryBoxPosition::NoTextBox) 9 | { 10 | setMouseCursor(MouseCursor::PointingHandCursor); 11 | setLookAndFeel(&mainSliderLookAndFeel); 12 | } 13 | 14 | ~RasterKnob() 15 | { 16 | setLookAndFeel(nullptr); 17 | } 18 | 19 | private: 20 | class RasterKnobLookAndFeel : public LookAndFeel_V4 21 | { 22 | public: 23 | RasterKnobLookAndFeel() 24 | { 25 | image = ImageCache::getFromMemory(BinaryData::RasterKnob_png, BinaryData::RasterKnob_pngSize); 26 | } 27 | 28 | void drawRotarySlider( 29 | Graphics& g, 30 | int x, 31 | int y, 32 | int width, 33 | int height, 34 | float sliderPosProportional, 35 | float /*rotaryStartAngle*/, 36 | float /*rotaryEndAngle*/, 37 | Slider& /*slider*/) override 38 | { 39 | const auto frames = 90; 40 | const auto frameId = static_cast(ceil(sliderPosProportional * (static_cast(frames) - 1.0f))); 41 | 42 | g.drawImage(image, 43 | x, 44 | y, 45 | width, 46 | height, 47 | (frameId % 9) * width, 48 | (frameId / 9) * height, 49 | width, 50 | height); 51 | } 52 | 53 | private: 54 | Image image; 55 | 56 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RasterKnobLookAndFeel) 57 | } mainSliderLookAndFeel; 58 | 59 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RasterKnob) 60 | }; 61 | -------------------------------------------------------------------------------- /Source/Components/VectorKnob.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class VectorKnob : public Slider 6 | { 7 | public: 8 | VectorKnob() : Slider(SliderStyle::RotaryHorizontalVerticalDrag, NoTextBox) 9 | { 10 | setMouseCursor(MouseCursor::PointingHandCursor); 11 | setLookAndFeel(&lookAndFeel); 12 | } 13 | 14 | ~VectorKnob() 15 | { 16 | setLookAndFeel(nullptr); 17 | } 18 | 19 | private: 20 | 21 | class VectorKnobLookAndFeel : public LookAndFeel_V4 22 | { 23 | public: 24 | void drawRotarySlider( 25 | Graphics& g, 26 | int x, 27 | int y, 28 | int width, 29 | int height, 30 | float sliderPosProportional, 31 | float rotaryStartAngle, 32 | float rotaryEndAngle, 33 | Slider& slider) override 34 | { 35 | if (const auto svg = XmlDocument::parse(BinaryData::VectorKnob_svg)) 36 | { 37 | const auto drawable = Drawable::createFromSVG(*svg); 38 | drawable->setTransformToFit(Rectangle(x, y, width, height).toFloat(), RectanglePlacement::centred); 39 | 40 | const float angle = rotaryStartAngle + sliderPosProportional * (rotaryEndAngle - rotaryStartAngle); 41 | drawable->draw(g, 1.f, AffineTransform::rotation(angle, width / 2.f, height / 2.f)); 42 | } 43 | } 44 | } lookAndFeel; 45 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(VectorKnob) 46 | }; 47 | -------------------------------------------------------------------------------- /Source/PluginProcessor.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin processor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #include "PluginProcessor.h" 10 | #include "RasterEditor.h" 11 | #include "VectorEditor.h" 12 | 13 | //============================================================================== 14 | ResizableAppAudioProcessor::ResizableAppAudioProcessor() 15 | #ifndef JucePlugin_PreferredChannelConfigurations 16 | : AudioProcessor (BusesProperties() 17 | #if ! JucePlugin_IsMidiEffect 18 | #if ! JucePlugin_IsSynth 19 | .withInput ("Input", juce::AudioChannelSet::stereo(), true) 20 | #endif 21 | .withOutput ("Output", juce::AudioChannelSet::stereo(), true) 22 | #endif 23 | ) 24 | #endif 25 | { 26 | } 27 | 28 | ResizableAppAudioProcessor::~ResizableAppAudioProcessor() 29 | { 30 | } 31 | 32 | //============================================================================== 33 | const juce::String ResizableAppAudioProcessor::getName() const 34 | { 35 | return JucePlugin_Name; 36 | } 37 | 38 | bool ResizableAppAudioProcessor::acceptsMidi() const 39 | { 40 | #if JucePlugin_WantsMidiInput 41 | return true; 42 | #else 43 | return false; 44 | #endif 45 | } 46 | 47 | bool ResizableAppAudioProcessor::producesMidi() const 48 | { 49 | #if JucePlugin_ProducesMidiOutput 50 | return true; 51 | #else 52 | return false; 53 | #endif 54 | } 55 | 56 | bool ResizableAppAudioProcessor::isMidiEffect() const 57 | { 58 | #if JucePlugin_IsMidiEffect 59 | return true; 60 | #else 61 | return false; 62 | #endif 63 | } 64 | 65 | double ResizableAppAudioProcessor::getTailLengthSeconds() const 66 | { 67 | return 0.0; 68 | } 69 | 70 | int ResizableAppAudioProcessor::getNumPrograms() 71 | { 72 | return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs, 73 | // so this should be at least 1, even if you're not really implementing programs. 74 | } 75 | 76 | int ResizableAppAudioProcessor::getCurrentProgram() 77 | { 78 | return 0; 79 | } 80 | 81 | void ResizableAppAudioProcessor::setCurrentProgram (int index) 82 | { 83 | } 84 | 85 | const juce::String ResizableAppAudioProcessor::getProgramName (int index) 86 | { 87 | return {}; 88 | } 89 | 90 | void ResizableAppAudioProcessor::changeProgramName (int index, const juce::String& newName) 91 | { 92 | } 93 | 94 | //============================================================================== 95 | void ResizableAppAudioProcessor::prepareToPlay (double sampleRate, int samplesPerBlock) 96 | { 97 | // Use this method as the place to do any pre-playback 98 | // initialisation that you need.. 99 | } 100 | 101 | void ResizableAppAudioProcessor::releaseResources() 102 | { 103 | // When playback stops, you can use this as an opportunity to free up any 104 | // spare memory, etc. 105 | } 106 | 107 | #ifndef JucePlugin_PreferredChannelConfigurations 108 | bool ResizableAppAudioProcessor::isBusesLayoutSupported (const BusesLayout& layouts) const 109 | { 110 | #if JucePlugin_IsMidiEffect 111 | juce::ignoreUnused (layouts); 112 | return true; 113 | #else 114 | // This is the place where you check if the layout is supported. 115 | // In this template code we only support mono or stereo. 116 | // Some plugin hosts, such as certain GarageBand versions, will only 117 | // load plugins that support stereo bus layouts. 118 | if (layouts.getMainOutputChannelSet() != juce::AudioChannelSet::mono() 119 | && layouts.getMainOutputChannelSet() != juce::AudioChannelSet::stereo()) 120 | return false; 121 | 122 | // This checks if the input layout matches the output layout 123 | #if ! JucePlugin_IsSynth 124 | if (layouts.getMainOutputChannelSet() != layouts.getMainInputChannelSet()) 125 | return false; 126 | #endif 127 | 128 | return true; 129 | #endif 130 | } 131 | #endif 132 | 133 | void ResizableAppAudioProcessor::processBlock (juce::AudioBuffer& buffer, juce::MidiBuffer& midiMessages) 134 | { 135 | juce::ScopedNoDenormals noDenormals; 136 | auto totalNumInputChannels = getTotalNumInputChannels(); 137 | auto totalNumOutputChannels = getTotalNumOutputChannels(); 138 | 139 | // In case we have more outputs than inputs, this code clears any output 140 | // channels that didn't contain input data, (because these aren't 141 | // guaranteed to be empty - they may contain garbage). 142 | // This is here to avoid people getting screaming feedback 143 | // when they first compile a plugin, but obviously you don't need to keep 144 | // this code if your algorithm always overwrites all the output channels. 145 | for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i) 146 | buffer.clear (i, 0, buffer.getNumSamples()); 147 | 148 | // This is the place where you'd normally do the guts of your plugin's 149 | // audio processing... 150 | // Make sure to reset the state if your inner loop is processing 151 | // the samples and the outer loop is handling the channels. 152 | // Alternatively, you can process the samples with the channels 153 | // interleaved by keeping the same state. 154 | for (int channel = 0; channel < totalNumInputChannels; ++channel) 155 | { 156 | auto* channelData = buffer.getWritePointer (channel); 157 | 158 | // ..do something to the data... 159 | } 160 | } 161 | 162 | //============================================================================== 163 | bool ResizableAppAudioProcessor::hasEditor() const 164 | { 165 | return true; // (change this to false if you choose to not supply an editor) 166 | } 167 | 168 | juce::AudioProcessorEditor* ResizableAppAudioProcessor::createEditor() 169 | { 170 | return new WrappedRasterAudioProcessorEditor (*this); 171 | } 172 | 173 | //============================================================================== 174 | void ResizableAppAudioProcessor::getStateInformation (juce::MemoryBlock& destData) 175 | { 176 | // You should use this method to store your parameters in the memory block. 177 | // You could do that either as raw data, or use the XML or ValueTree classes 178 | // as intermediaries to make it easy to save and load complex data. 179 | } 180 | 181 | void ResizableAppAudioProcessor::setStateInformation (const void* data, int sizeInBytes) 182 | { 183 | // You should use this method to restore your parameters from this memory block, 184 | // whose contents will have been created by the getStateInformation() call. 185 | } 186 | 187 | //============================================================================== 188 | // This creates new instances of the plugin.. 189 | juce::AudioProcessor* JUCE_CALLTYPE createPluginFilter() 190 | { 191 | return new ResizableAppAudioProcessor(); 192 | } 193 | -------------------------------------------------------------------------------- /Source/PluginProcessor.h: -------------------------------------------------------------------------------- 1 | /* 2 | ============================================================================== 3 | 4 | This file contains the basic framework code for a JUCE plugin processor. 5 | 6 | ============================================================================== 7 | */ 8 | 9 | #pragma once 10 | 11 | #include 12 | 13 | //============================================================================== 14 | /** 15 | */ 16 | class ResizableAppAudioProcessor : public juce::AudioProcessor 17 | #if JucePlugin_Enable_ARA 18 | , public juce::AudioProcessorARAExtension 19 | #endif 20 | { 21 | public: 22 | //============================================================================== 23 | ResizableAppAudioProcessor(); 24 | ~ResizableAppAudioProcessor() override; 25 | 26 | //============================================================================== 27 | void prepareToPlay (double sampleRate, int samplesPerBlock) override; 28 | void releaseResources() override; 29 | 30 | #ifndef JucePlugin_PreferredChannelConfigurations 31 | bool isBusesLayoutSupported (const BusesLayout& layouts) const override; 32 | #endif 33 | 34 | void processBlock (juce::AudioBuffer&, juce::MidiBuffer&) override; 35 | 36 | //============================================================================== 37 | juce::AudioProcessorEditor* createEditor() override; 38 | bool hasEditor() const override; 39 | 40 | //============================================================================== 41 | const juce::String getName() const override; 42 | 43 | bool acceptsMidi() const override; 44 | bool producesMidi() const override; 45 | bool isMidiEffect() const override; 46 | double getTailLengthSeconds() const override; 47 | 48 | //============================================================================== 49 | int getNumPrograms() override; 50 | int getCurrentProgram() override; 51 | void setCurrentProgram (int index) override; 52 | const juce::String getProgramName (int index) override; 53 | void changeProgramName (int index, const juce::String& newName) override; 54 | 55 | //============================================================================== 56 | void getStateInformation (juce::MemoryBlock& destData) override; 57 | void setStateInformation (const void* data, int sizeInBytes) override; 58 | 59 | private: 60 | //============================================================================== 61 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ResizableAppAudioProcessor) 62 | }; 63 | -------------------------------------------------------------------------------- /Source/RasterEditor.cpp: -------------------------------------------------------------------------------- 1 | #include "PluginProcessor.h" 2 | #include "RasterEditor.h" 3 | 4 | RasterComponent::RasterComponent (ResizableAppAudioProcessor& p) : audioProcessor (p) 5 | { 6 | addAndMakeVisible(slider); 7 | } 8 | 9 | RasterComponent::~RasterComponent() 10 | { 11 | } 12 | 13 | void RasterComponent::paint (juce::Graphics& g) 14 | { 15 | g.drawImage(ImageCache::getFromMemory(BinaryData::Background_png, BinaryData::Background_pngSize), getLocalBounds().toFloat()); 16 | } 17 | 18 | void RasterComponent::resized() 19 | { 20 | slider.setBounds(631, 393, 233, 233); 21 | } 22 | 23 | 24 | // Wrapper implementation 25 | 26 | WrappedRasterAudioProcessorEditor::WrappedRasterAudioProcessorEditor(ResizableAppAudioProcessor& p) : 27 | AudioProcessorEditor(p), 28 | rasterComponent(p) 29 | { 30 | addAndMakeVisible(rasterComponent); 31 | 32 | PropertiesFile::Options options; 33 | options.applicationName = ProjectInfo::projectName; 34 | options.commonToAllUsers = true; 35 | options.filenameSuffix = "settings"; 36 | options.osxLibrarySubFolder = "Application Support"; 37 | applicationProperties.setStorageParameters(options); 38 | 39 | if (auto* constrainer = getConstrainer()) 40 | { 41 | constrainer->setFixedAspectRatio(static_cast (originalWidth) / static_cast (originalHeight)); 42 | constrainer->setSizeLimits(originalWidth / 4, originalHeight / 4, 43 | originalWidth, originalHeight); 44 | } 45 | 46 | auto sizeRatio{ 1.0 }; 47 | if (auto* properties = applicationProperties.getCommonSettings(true)) 48 | { 49 | sizeRatio = properties->getDoubleValue("sizeRatio", 1.0); 50 | } 51 | 52 | setResizable(true, true); 53 | setSize(static_cast (originalWidth * sizeRatio), 54 | static_cast (originalHeight * sizeRatio)); 55 | } 56 | 57 | void WrappedRasterAudioProcessorEditor::resized() 58 | { 59 | const auto scaleFactor = static_cast (getWidth()) / originalWidth; 60 | if (auto* properties = applicationProperties.getCommonSettings(true)) 61 | { 62 | properties->setValue("sizeRatio", scaleFactor); 63 | } 64 | 65 | rasterComponent.setTransform(AffineTransform::scale(scaleFactor)); 66 | rasterComponent.setBounds(0, 0, originalWidth, originalHeight); 67 | } 68 | -------------------------------------------------------------------------------- /Source/RasterEditor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "PluginProcessor.h" 5 | #include "Components/RasterKnob.h" 6 | 7 | class RasterComponent : public Component 8 | { 9 | public: 10 | RasterComponent (ResizableAppAudioProcessor&); 11 | ~RasterComponent() override; 12 | 13 | void paint (Graphics&) override; 14 | void resized() override; 15 | 16 | private: 17 | ResizableAppAudioProcessor& audioProcessor; 18 | RasterKnob slider; 19 | 20 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (RasterComponent) 21 | }; 22 | 23 | // Root Wrapper 24 | 25 | class WrappedRasterAudioProcessorEditor : public AudioProcessorEditor 26 | { 27 | public: 28 | WrappedRasterAudioProcessorEditor(ResizableAppAudioProcessor&); 29 | void resized() override; 30 | 31 | private: 32 | static constexpr int originalWidth{ 1200 }; 33 | static constexpr int originalHeight{ 800 }; 34 | 35 | RasterComponent rasterComponent; 36 | ApplicationProperties applicationProperties; 37 | }; 38 | -------------------------------------------------------------------------------- /Source/VectorEditor.cpp: -------------------------------------------------------------------------------- 1 | #include "PluginProcessor.h" 2 | #include "VectorEditor.h" 3 | 4 | VectorAudioProcessorEditor::VectorAudioProcessorEditor(ResizableAppAudioProcessor& p) 5 | : AudioProcessorEditor(&p), audioProcessor(p) 6 | { 7 | addAndMakeVisible(slider); 8 | 9 | setResizable(true, true); 10 | setSize(400, 300); 11 | } 12 | 13 | VectorAudioProcessorEditor::~VectorAudioProcessorEditor() 14 | { 15 | } 16 | 17 | void VectorAudioProcessorEditor::paint(juce::Graphics& g) 18 | { 19 | g.setGradientFill(ColourGradient{ Colours::darkgrey, getLocalBounds().toFloat().getCentre(), Colours::darkgrey.darker(0.7f), {}, true }); 20 | g.fillRect(getLocalBounds()); 21 | } 22 | 23 | void VectorAudioProcessorEditor::resized() 24 | { 25 | const auto dimensions = jmin(proportionOfWidth(0.25f), proportionOfHeight(0.25f)); 26 | slider.setBounds(getLocalBounds().withSizeKeepingCentre(dimensions, dimensions)); 27 | } 28 | -------------------------------------------------------------------------------- /Source/VectorEditor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "PluginProcessor.h" 5 | #include "Components/VectorKnob.h" 6 | 7 | class VectorAudioProcessorEditor : public juce::AudioProcessorEditor 8 | { 9 | public: 10 | VectorAudioProcessorEditor(ResizableAppAudioProcessor&); 11 | ~VectorAudioProcessorEditor() override; 12 | 13 | void paint(juce::Graphics&) override; 14 | void resized() override; 15 | 16 | private: 17 | ResizableAppAudioProcessor& audioProcessor; 18 | 19 | VectorKnob slider; 20 | 21 | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(VectorAudioProcessorEditor) 22 | }; 23 | --------------------------------------------------------------------------------