├── .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 |
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 |
--------------------------------------------------------------------------------