54 short numIns, numOuts;
73 const String &preferredDefaultDeviceName = String(),
74 const AudioDeviceManager::AudioDeviceSetup *preferredSetupOptions =
nullptr,
75 const Array<PluginInOuts> &channels = Array<PluginInOuts>(),
76#
if JUCE_ANDROID || JUCE_IOS
77 bool shouldAutoOpenMidiDevices =
true
79 bool shouldAutoOpenMidiDevices =
false
83 : settings(settingsToUse, takeOwnershipOfSettings), channelConfiguration(channels),
84 autoOpenMidiDevices(shouldAutoOpenMidiDevices)
86 shouldMuteInput.addListener(
this);
87 shouldMuteInput = !isInterAppAudioConnected();
91 auto inChannels = (channelConfiguration.size() > 0 ? channelConfiguration[0].numIns
92 : processor->getMainBusNumInputChannels());
94 if (preferredSetupOptions !=
nullptr)
95 options.reset(
new AudioDeviceManager::AudioDeviceSetup(*preferredSetupOptions));
97 auto audioInputRequired = (inChannels > 0);
99 if (audioInputRequired && RuntimePermissions::isRequired(RuntimePermissions::recordAudio) &&
100 !RuntimePermissions::isGranted(RuntimePermissions::recordAudio))
101 RuntimePermissions::request(
102 RuntimePermissions::recordAudio,
103 [
this, preferredDefaultDeviceName](
bool granted) { init(granted, preferredDefaultDeviceName); });
105 init(audioInputRequired, preferredDefaultDeviceName);
108 void init(
bool enableAudioInput,
const String &preferredDefaultDeviceName)
110 setupAudioDevices(enableAudioInput, preferredDefaultDeviceName, options.get());
114 if (autoOpenMidiDevices)
118 ~StandalonePluginHolder()
override
122 handleDeletePlugin();
123 shutDownAudioDevices();
127 virtual void createPlugin()
129 handleCreatePlugin();
132 virtual void deletePlugin()
134 handleDeletePlugin();
137 int getNumInputChannels()
const
139 if (processor ==
nullptr)
142 return (channelConfiguration.size() > 0 ? channelConfiguration[0].numIns
143 : processor->getMainBusNumInputChannels());
146 int getNumOutputChannels()
const
148 if (processor ==
nullptr)
151 return (channelConfiguration.size() > 0 ? channelConfiguration[0].numOuts
152 : processor->getMainBusNumOutputChannels());
155 static String getFilePatterns(
const String &fileSuffix)
157 if (fileSuffix.isEmpty())
160 return (fileSuffix.startsWithChar(
'.') ?
"*" :
"*.") + fileSuffix;
164 Value &getMuteInputValue()
166 return shouldMuteInput;
168 bool getProcessorHasPotentialFeedbackLoop()
const
170 return processorHasPotentialFeedbackLoop;
172 void valueChanged(Value &value)
override
174 muteInput = (bool)value.getValue();
178 File getLastFile()
const
182 if (settings !=
nullptr)
183 f = File(settings->getValue(
"lastStateFile"));
186 f = File::getSpecialLocation(File::userDocumentsDirectory);
191 void setLastFile(
const FileChooser &fc)
193 if (settings !=
nullptr)
194 settings->setValue(
"lastStateFile", fc.getResult().getFullPathName());
201 std::make_unique<FileChooser>(TRANS(
"Save current state"), getLastFile(), getFilePatterns(fileSuffix));
202 auto flags = FileBrowserComponent::saveMode | FileBrowserComponent::canSelectFiles |
203 FileBrowserComponent::warnAboutOverwriting;
205 stateFileChooser->launchAsync(flags, [
this](
const FileChooser &fc) {
206 if (fc.getResult() == File{})
212 processor->getStateInformation(data);
214 if (!fc.getResult().replaceWithData(data.getData(), data.getSize()))
216 auto opts = MessageBoxOptions::makeOptionsOk(AlertWindow::WarningIcon, TRANS(
"Error whilst saving"),
217 TRANS(
"Couldn't write to the specified file!"));
218 messageBox = AlertWindow::showScopedAsync(opts,
nullptr);
227 std::make_unique<FileChooser>(TRANS(
"Load a saved state"), getLastFile(), getFilePatterns(fileSuffix));
228 auto flags = FileBrowserComponent::openMode | FileBrowserComponent::canSelectFiles;
230 stateFileChooser->launchAsync(flags, [
this](
const FileChooser &fc) {
231 if (fc.getResult() == File{})
238 if (fc.getResult().loadFileAsData(data))
240 processor->setStateInformation(data.getData(), (
int)data.getSize());
244 auto opts = MessageBoxOptions::makeOptionsOk(AlertWindow::WarningIcon, TRANS(
"Error whilst loading"),
245 TRANS(
"Couldn't read from the specified file!"));
246 messageBox = AlertWindow::showScopedAsync(opts,
nullptr);
254 player.setProcessor(processor.get());
256#if JucePlugin_Enable_IAA && JUCE_IOS
257 if (
auto device =
dynamic_cast<iOSAudioIODevice *
>(deviceManager.getCurrentAudioDevice()))
259 processor->setPlayHead(device->getAudioPlayHead());
260 device->setMidiMessageCollector(&player.getMidiMessageCollector());
267 player.setProcessor(
nullptr);
274 DialogWindow::LaunchOptions o;
276 int maxNumInputs = 0, maxNumOutputs = 0;
278 if (channelConfiguration.size() > 0)
280 auto &defaultConfig = channelConfiguration.getReference(0);
282 maxNumInputs = jmax(0, (
int)defaultConfig.numIns);
283 maxNumOutputs = jmax(0, (
int)defaultConfig.numOuts);
286 if (
auto *bus = processor->getBus(
true, 0))
287 maxNumInputs = jmax(0, bus->getDefaultLayout().size());
289 if (
auto *bus = processor->getBus(
false, 0))
290 maxNumOutputs = jmax(0, bus->getDefaultLayout().size());
292 auto content = std::make_unique<SettingsComponent>(*
this, deviceManager, maxNumInputs, maxNumOutputs);
293 content->setSize(500, 550);
294 content->setToRecommendedSize();
296 o.content.setOwned(content.release());
298 o.dialogTitle = TRANS(
"Audio Settings");
299 o.dialogBackgroundColour = o.content->getLookAndFeel().findColour(ResizableWindow::backgroundColourId);
300 o.escapeKeyTriggersCloseButton =
true;
301 o.useNativeTitleBar =
true;
307 void saveAudioDeviceState()
309 if (settings !=
nullptr)
311 auto xml = deviceManager.createStateXml();
313 settings->setValue(
"audioSetup", xml.get());
315#if !(JUCE_IOS || JUCE_ANDROID)
316 settings->setValue(
"shouldMuteInput", (
bool)shouldMuteInput.getValue());
321 void reloadAudioDeviceState(
bool enableAudioInput,
const String &preferredDefaultDeviceName,
322 const AudioDeviceManager::AudioDeviceSetup *preferredSetupOptions)
324 std::unique_ptr<XmlElement> savedState;
326 if (settings !=
nullptr)
328 savedState = settings->getXmlValue(
"audioSetup");
330#if !(JUCE_IOS || JUCE_ANDROID)
331 shouldMuteInput.setValue(settings->getBoolValue(
"shouldMuteInput",
true));
335 auto inputChannels = getNumInputChannels();
336 auto outputChannels = getNumOutputChannels();
338 if (inputChannels == 0 && outputChannels == 0 && processor->isMidiEffect())
344 deviceManager.initialise(enableAudioInput ? inputChannels : 0, outputChannels, savedState.get(),
true,
345 preferredDefaultDeviceName, preferredSetupOptions);
349 void savePluginState()
351 if (settings !=
nullptr && processor !=
nullptr)
354 processor->getStateInformation(data);
356 settings->setValue(
"filterState", data.toBase64Encoding());
360 void reloadPluginState()
362 if (settings !=
nullptr)
366 if (data.fromBase64Encoding(settings->getValue(
"filterState")) && data.getSize() > 0)
367 processor->setStateInformation(data.getData(), (
int)data.getSize());
372 void switchToHostApplication()
375 if (
auto device =
dynamic_cast<iOSAudioIODevice *
>(deviceManager.getCurrentAudioDevice()))
376 device->switchApplication();
380 bool isInterAppAudioConnected()
383 if (
auto device =
dynamic_cast<iOSAudioIODevice *
>(deviceManager.getCurrentAudioDevice()))
384 return device->isInterAppAudioConnected();
390 Image getIAAHostIcon([[maybe_unused]]
int size)
392#if JUCE_IOS && JucePlugin_Enable_IAA
393 if (
auto device =
dynamic_cast<iOSAudioIODevice *
>(deviceManager.getCurrentAudioDevice()))
394 return device->getIcon(size);
404 OptionalScopedPointer<PropertySet> settings;
405 std::unique_ptr<AudioProcessor> processor;
406 AudioDeviceManager deviceManager;
407 AudioProcessorPlayer player;
408 Array<PluginInOuts> channelConfiguration;
411 bool processorHasPotentialFeedbackLoop =
true;
412 std::atomic<bool> muteInput{
true};
413 Value shouldMuteInput;
414 AudioBuffer<float> emptyBuffer;
415 bool autoOpenMidiDevices;
417 std::unique_ptr<AudioDeviceManager::AudioDeviceSetup> options;
418 Array<MidiDeviceInfo> lastMidiDevices;
420 std::unique_ptr<FileChooser> stateFileChooser;
421 ScopedMessageBox messageBox;
425 void handleCreatePlugin()
427 processor = createPluginFilterOfType(AudioProcessor::wrapperType_Standalone);
428 processor->disableNonMainBuses();
429 processor->setRateAndBufferSizeDetails(44100, 512);
431 processorHasPotentialFeedbackLoop = (getNumInputChannels() > 0 && getNumOutputChannels() > 0);
434 void handleDeletePlugin()
454 class CallbackMaxSizeEnforcer :
public AudioIODeviceCallback
457 explicit CallbackMaxSizeEnforcer(AudioIODeviceCallback &callbackIn) : inner(callbackIn)
461 void audioDeviceAboutToStart(AudioIODevice *device)
override
463 maximumSize = device->getCurrentBufferSizeSamples();
464 storedInputChannels.resize((
size_t)device->getActiveInputChannels().countNumberOfSetBits());
465 storedOutputChannels.resize((
size_t)device->getActiveOutputChannels().countNumberOfSetBits());
467 inner.audioDeviceAboutToStart(device);
470 void audioDeviceIOCallbackWithContext(
const float *
const *inputChannelData,
471 [[maybe_unused]]
int numInputChannels,
float *
const *outputChannelData,
472 [[maybe_unused]]
int numOutputChannels,
int numSamples,
473 const AudioIODeviceCallbackContext &context)
override
475 jassert((
int)storedInputChannels.size() == numInputChannels);
476 jassert((
int)storedOutputChannels.size() == numOutputChannels);
480 while (position < numSamples)
482 const auto blockLength = jmin(maximumSize, numSamples - position);
484 initChannelPointers(inputChannelData, storedInputChannels, position);
485 initChannelPointers(outputChannelData, storedOutputChannels, position);
487 inner.audioDeviceIOCallbackWithContext(storedInputChannels.data(), (
int)storedInputChannels.size(),
488 storedOutputChannels.data(), (
int)storedOutputChannels.size(),
489 blockLength, context);
491 position += blockLength;
495 void audioDeviceStopped()
override
497 inner.audioDeviceStopped();
501 struct GetChannelWithOffset
505 template <
typename Ptr>
auto operator()(Ptr ptr)
const noexcept -> Ptr
511 template <
typename Ptr,
typename Vector>
void initChannelPointers(Ptr &&source, Vector &&target,
int offset)
513 std::transform(source, source + target.size(), target.begin(), GetChannelWithOffset{offset});
516 AudioIODeviceCallback &inner;
518 std::vector<const float *> storedInputChannels;
519 std::vector<float *> storedOutputChannels;
522 CallbackMaxSizeEnforcer maxSizeEnforcer{*
this};
525 class SettingsComponent :
public Component
529 int maxAudioInputChannels,
int maxAudioOutputChannels)
530 : owner(pluginHolder), deviceSelector(deviceManagerToUse, 0, maxAudioInputChannels, 0,
531 maxAudioOutputChannels, false, false, false, false),
532 shouldMuteLabel(
"Feedback Loop:",
"Feedback Loop:"), shouldMuteButton(
"Mute audio input")
536 shouldMuteButton.setClickingTogglesState(
true);
537 shouldMuteButton.getToggleStateValue().referTo(owner.shouldMuteInput);
539 juce::LookAndFeel::getDefaultLookAndFeel().setColour(juce::ComboBox::backgroundColourId,
540 juce::Colours::black);
541 juce::LookAndFeel::getDefaultLookAndFeel().setColour(juce::ListBox::backgroundColourId,
542 juce::Colours::black);
543 juce::LookAndFeel::getDefaultLookAndFeel().setColour(juce::TextButton::buttonColourId,
544 juce::Colours::black);
545 juce::LookAndFeel::getDefaultLookAndFeel().setColour(juce::ScrollBar::thumbColourId, juce::Colours::white);
546 juce::LookAndFeel::getDefaultLookAndFeel().setColour(juce::PopupMenu::backgroundColourId,
547 juce::Colours::black);
549 addAndMakeVisible(deviceSelector);
552 if (owner.getProcessorHasPotentialFeedbackLoop())
561 void paint(Graphics &g)
override
564 g.fillAll(juce::Colours::black);
567 void resized()
override
569 const ScopedValueSetter<bool> scope(isResizing,
true);
571 auto r = getLocalBounds();
573 if (owner.getProcessorHasPotentialFeedbackLoop())
575 auto itemHeight = deviceSelector.getItemHeight();
576 auto extra = r.removeFromTop(itemHeight);
578 auto seperatorHeight = (itemHeight >> 1);
579 shouldMuteButton.setBounds(Rectangle<int>(extra.proportionOfWidth(0.35f), seperatorHeight,
580 extra.proportionOfWidth(0.60f),
581 deviceSelector.getItemHeight()));
583 r.removeFromTop(seperatorHeight);
586 deviceSelector.setBounds(r);
589 void childBoundsChanged(Component *childComp)
override
591 if (!isResizing && childComp == &deviceSelector)
592 setToRecommendedSize();
595 void setToRecommendedSize()
597 const auto extraHeight = [&] {
598 if (!owner.getProcessorHasPotentialFeedbackLoop())
601 const auto itemHeight = deviceSelector.getItemHeight();
602 const auto separatorHeight = (itemHeight >> 1);
603 return itemHeight + separatorHeight;
606 setSize(getWidth(), deviceSelector.getHeight() + extraHeight);
612 AudioDeviceSelectorComponent deviceSelector;
614 Label shouldMuteLabel;
615 ToggleButton shouldMuteButton;
616 bool isResizing =
false;
619 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(SettingsComponent)
623 void audioDeviceIOCallbackWithContext(
const float *
const *inputChannelData,
int numInputChannels,
624 float *
const *outputChannelData,
int numOutputChannels,
int numSamples,
625 const AudioIODeviceCallbackContext &context)
override
630 inputChannelData = emptyBuffer.getArrayOfReadPointers();
633 player.audioDeviceIOCallbackWithContext(inputChannelData, numInputChannels, outputChannelData,
634 numOutputChannels, numSamples, context);
637 void audioDeviceAboutToStart(AudioIODevice *device)
override
639 emptyBuffer.setSize(device->getActiveInputChannels().countNumberOfSetBits(),
640 device->getCurrentBufferSizeSamples());
643 player.audioDeviceAboutToStart(device);
644 player.setMidiOutput(deviceManager.getDefaultMidiOutput());
647 void audioDeviceStopped()
override
649 player.setMidiOutput(
nullptr);
650 player.audioDeviceStopped();
651 emptyBuffer.setSize(0, 0);
655 void setupAudioDevices(
bool enableAudioInput,
const String &preferredDefaultDeviceName,
656 const AudioDeviceManager::AudioDeviceSetup *preferredSetupOptions)
658 deviceManager.addAudioCallback(&maxSizeEnforcer);
659 deviceManager.addMidiInputDeviceCallback({}, &player);
661 reloadAudioDeviceState(enableAudioInput, preferredDefaultDeviceName, preferredSetupOptions);
664 void shutDownAudioDevices()
666 saveAudioDeviceState();
668 deviceManager.removeMidiInputDeviceCallback({}, &player);
669 deviceManager.removeAudioCallback(&maxSizeEnforcer);
672 void timerCallback()
override
674 auto newMidiDevices = MidiInput::getAvailableDevices();
676 if (newMidiDevices != lastMidiDevices)
678 for (
auto &oldDevice : lastMidiDevices)
679 if (!newMidiDevices.contains(oldDevice))
680 deviceManager.setMidiInputDeviceEnabled(oldDevice.identifier,
false);
682 for (
auto &newDevice : newMidiDevices)
683 if (!lastMidiDevices.contains(newDevice))
684 deviceManager.setMidiInputDeviceEnabled(newDevice.identifier,
true);
686 lastMidiDevices = newMidiDevices;
716 bool takeOwnershipOfSettings,
const String &preferredDefaultDeviceName = String(),
717 const AudioDeviceManager::AudioDeviceSetup *preferredSetupOptions =
nullptr,
718 const Array<PluginInOuts> &constrainToConfiguration = {},
719#if JUCE_ANDROID || JUCE_IOS
720 bool autoOpenMidiDevices =
true
722 bool autoOpenMidiDevices =
false
725 : DocumentWindow(title, backgroundColour, DocumentWindow::minimiseButton | DocumentWindow::closeButton),
726 optionsButton(
"Options")
728 setConstrainer(&decoratorConstrainer);
730#if JUCE_IOS || JUCE_ANDROID
731 setTitleBarHeight(0);
734 setUsingNativeTitleBar(
true);
735 setTitleBarButtonsRequired(DocumentWindow::minimiseButton | DocumentWindow::closeButton,
false);
737 Component::addAndMakeVisible(optionsButton);
738 optionsButton.addListener(
this);
739 optionsButton.setTriggeredOnMouseDown(
true);
743 preferredDefaultDeviceName, preferredSetupOptions,
744 constrainToConfiguration, autoOpenMidiDevices));
746#if JUCE_IOS || JUCE_ANDROID
752 const auto windowScreenBounds = [
this]() -> Rectangle<int> {
753 const auto width = getWidth();
754 const auto height = getHeight();
756 const auto &displays = Desktop::getInstance().getDisplays();
758 if (
auto *props = pluginHolder->settings.get())
760 constexpr int defaultValue = -100;
762 const auto x = props->getIntValue(
"windowX", defaultValue);
763 const auto y = props->getIntValue(
"windowY", defaultValue);
765 if (x != defaultValue && y != defaultValue)
767 const auto screenLimits = displays.getDisplayForRect({x, y, width, height})->userArea;
770 jlimit(screenLimits.getX(), jmax(screenLimits.getX(), screenLimits.getRight() - width), x),
771 jlimit(screenLimits.getY(), jmax(screenLimits.getY(), screenLimits.getBottom() - height), y),
776 const auto displayArea = displays.getPrimaryDisplay()->userArea;
778 return {displayArea.getCentreX() - width / 2, displayArea.getCentreY() - height / 2, width, height};
781 setBoundsConstrained(windowScreenBounds);
783 if (
auto *processor = getAudioProcessor())
784 if (
auto *editor = processor->getActiveEditor())
785 setResizable(editor->isResizable(),
false);
789 ~StandaloneFilterWindow()
override
791#if (!JUCE_IOS) && (!JUCE_ANDROID)
792 if (
auto *props = pluginHolder->settings.get())
794 props->setValue(
"windowX", getX());
795 props->setValue(
"windowY", getY());
799 pluginHolder->stopPlaying();
800 clearContentComponent();
801 pluginHolder =
nullptr;
805 AudioProcessor *getAudioProcessor() const noexcept
807 return pluginHolder->processor.get();
809 AudioDeviceManager &getDeviceManager() const noexcept
811 return pluginHolder->deviceManager;
817 pluginHolder->stopPlaying();
818 clearContentComponent();
819 pluginHolder->deletePlugin();
821 if (
auto *props = pluginHolder->settings.get())
822 props->removeValue(
"filterState");
824 pluginHolder->createPlugin();
826 pluginHolder->startPlaying();
830 void closeButtonPressed()
override
832 pluginHolder->savePluginState();
834 JUCEApplicationBase::quit();
837 void handleMenuResult(
int result)
842 pluginHolder->showAudioSettingsDialog();
845 pluginHolder->askUserToSaveState();
848 pluginHolder->askUserToLoadState();
860 if (button !=
nullptr && result != 0)
861 button->handleMenuResult(result);
864 void resized()
override
866 DocumentWindow::resized();
867 optionsButton.setBounds(8, 6, 60, getTitleBarHeight() - 8);
870 virtual StandalonePluginHolder *getPluginHolder()
872 return pluginHolder.get();
875 std::unique_ptr<StandalonePluginHolder> pluginHolder;
880 auto *content =
new MainContentComponent(*
this);
881 decoratorConstrainer.setMainContentComponent(content);
883#if JUCE_IOS || JUCE_ANDROID
884 constexpr auto resizeAutomatically =
false;
886 constexpr auto resizeAutomatically =
true;
889 setContentOwned(content, resizeAutomatically);
892 void buttonClicked(Button *)
override
895 m.addItem(1, TRANS(
"Audio/MIDI Settings..."));
897 m.addItem(2, TRANS(
"Save current state..."));
898 m.addItem(3, TRANS(
"Load a saved state..."));
900 m.addItem(4, TRANS(
"Reset to default state"));
902 m.showMenuAsync(PopupMenu::Options(), ModalCallbackFunction::forComponent(menuCallback,
this));
906 class MainContentComponent :
public Component,
907 private Value::Listener,
908 private Button::Listener,
909 private ComponentListener
913 : owner(filterWindow), notification(this),
914 editor(owner.getAudioProcessor()->hasEditor()
915 ? owner.getAudioProcessor()->createEditorIfNeeded()
916 : new GenericAudioProcessorEditor(*owner.getAudioProcessor()))
918 inputMutedValue.referTo(owner.pluginHolder->getMuteInputValue());
920 if (editor !=
nullptr)
922 editor->addComponentListener(
this);
923 handleMovedOrResized();
925 addAndMakeVisible(editor.get());
928 addChildComponent(notification);
930 if (owner.pluginHolder->getProcessorHasPotentialFeedbackLoop())
932 inputMutedValue.addListener(
this);
933 shouldShowNotification = inputMutedValue.getValue();
936 inputMutedChanged(shouldShowNotification);
939 ~MainContentComponent()
override
941 if (editor !=
nullptr)
943 editor->removeComponentListener(
this);
944 owner.pluginHolder->processor->editorBeingDeleted(editor.get());
949 void resized()
override
954 ComponentBoundsConstrainer *getEditorConstrainer()
const
956 if (
auto *e = editor.get())
957 return e->getConstrainer();
962 BorderSize<int> computeBorder()
const
964 const auto nativeFrame = [&]() -> BorderSize<int> {
965 if (
auto *peer = owner.getPeer())
966 if (
const auto frameSize = peer->getFrameSizeIfPresent())
972 return nativeFrame.addedTo(owner.getContentComponentBorder())
973 .addedTo(BorderSize<int>{shouldShowNotification ? NotificationArea::height : 0, 0, 0, 0});
978 class NotificationArea :
public Component
986 NotificationArea(Button::Listener *settingsButtonListener)
987 : notification(
"notification",
"Audio input is muted to avoid feedback loop"),
988#if JUCE_IOS || JUCE_ANDROID
989 settingsButton(
"Unmute Input")
991 settingsButton(
"Settings...")
996 notification.setColour(Label::textColourId, Colours::black);
998 settingsButton.addListener(settingsButtonListener);
1000 addAndMakeVisible(notification);
1001 addAndMakeVisible(settingsButton);
1004 void paint(Graphics &g)
override
1006 auto r = getLocalBounds();
1008 g.setColour(Colours::darkgoldenrod);
1009 g.fillRect(r.removeFromBottom(1));
1011 g.setColour(Colours::lightgoldenrodyellow);
1015 void resized()
override
1017 auto r = getLocalBounds().reduced(5);
1019 settingsButton.setBounds(r.removeFromRight(70));
1020 notification.setBounds(r);
1025 TextButton settingsButton;
1029 void inputMutedChanged(
bool newInputMutedValue)
1031 shouldShowNotification = newInputMutedValue;
1032 notification.setVisible(shouldShowNotification);
1034#if JUCE_IOS || JUCE_ANDROID
1037 if (editor !=
nullptr)
1039 const int extraHeight = shouldShowNotification ? NotificationArea::height : 0;
1040 const auto rect = getSizeToContainEditor();
1041 setSize(rect.getWidth(), rect.getHeight() + extraHeight);
1046 void valueChanged(Value &value)
override
1048 inputMutedChanged(value.getValue());
1050 void buttonClicked(Button *)
override
1052#if JUCE_IOS || JUCE_ANDROID
1053 owner.pluginHolder->getMuteInputValue().setValue(
false);
1055 owner.pluginHolder->showAudioSettingsDialog();
1060 void handleResized()
1062 auto r = getLocalBounds();
1064 if (shouldShowNotification)
1065 notification.setBounds(r.removeFromTop(NotificationArea::height));
1067 if (editor !=
nullptr)
1069 const auto newPos = r.getTopLeft().toFloat().transformedBy(editor->getTransform().inverted());
1071 if (preventResizingEditor)
1072 editor->setTopLeftPosition(newPos.roundToInt());
1074 editor->setBoundsConstrained(
1075 editor->getLocalArea(
this, r.toFloat()).withPosition(newPos).toNearestInt());
1079 void handleMovedOrResized()
1081 const ScopedValueSetter<bool> scope(preventResizingEditor,
true);
1083 if (editor !=
nullptr)
1085 auto rect = getSizeToContainEditor();
1087 setSize(rect.getWidth(), rect.getHeight() + (shouldShowNotification ? NotificationArea::height : 0));
1091 void componentMovedOrResized(Component &,
bool,
bool)
override
1093 handleMovedOrResized();
1096 Rectangle<int> getSizeToContainEditor()
const
1098 if (editor !=
nullptr)
1099 return getLocalArea(editor.get(), editor->getLocalBounds());
1106 NotificationArea notification;
1107 std::unique_ptr<AudioProcessorEditor> editor;
1108 Value inputMutedValue;
1109 bool shouldShowNotification =
false;
1110 bool preventResizingEditor =
false;
1112 JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainContentComponent)
1127 class DecoratorConstrainer :
public BorderedComponentBoundsConstrainer
1130 ComponentBoundsConstrainer *getWrappedConstrainer()
const override
1132 return contentComponent !=
nullptr ? contentComponent->getEditorConstrainer() :
nullptr;
1135 BorderSize<int> getAdditionalBorder()
const override
1137 return contentComponent !=
nullptr ? contentComponent->computeBorder() : BorderSize<int>{};
1140 void setMainContentComponent(MainContentComponent *in)
1142 contentComponent = in;
1146 MainContentComponent *contentComponent =
nullptr;
1150 TextButton optionsButton;
1151 DecoratorConstrainer decoratorConstrainer;