From 5adcad4a7a4e693be649ab53b14e486a98deaa1d Mon Sep 17 00:00:00 2001 From: George Reales Date: Thu, 10 Mar 2022 18:45:34 +0100 Subject: [PATCH] 6.1.6 framework update and tooltip enhancements --- .../Standalone/juce_StandaloneFilterWindow.h | 513 +++++++++++++----- OB-Xd.jucer | 15 +- README.md | 2 +- Source/Gui/Knob.h | 1 - Source/PluginEditor.cpp | 27 +- Source/PluginProcessor.cpp | 16 +- Source/PluginProcessor.h | 9 + 7 files changed, 432 insertions(+), 151 deletions(-) diff --git a/Modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h b/Modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h index 716c570..bbcd089 100644 --- a/Modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h +++ b/Modules/juce_audio_plugin_client/Standalone/juce_StandaloneFilterWindow.h @@ -113,7 +113,7 @@ public: startTimer (500); } - virtual ~StandalonePluginHolder() override + ~StandalonePluginHolder() override { stopTimer(); @@ -128,13 +128,7 @@ public: processor->disableNonMainBuses(); processor->setRateAndBufferSizeDetails (44100, 512); - int inChannels = (channelConfiguration.size() > 0 ? channelConfiguration[0].numIns - : processor->getMainBusNumInputChannels()); - - int outChannels = (channelConfiguration.size() > 0 ? channelConfiguration[0].numOuts - : processor->getMainBusNumOutputChannels()); - - processorHasPotentialFeedbackLoop = (inChannels > 0 && outChannels > 0); + processorHasPotentialFeedbackLoop = (getNumInputChannels() > 0 && getNumOutputChannels() > 0); } virtual void deletePlugin() @@ -143,6 +137,24 @@ public: processor = nullptr; } + int getNumInputChannels() const + { + if (processor == nullptr) + return 0; + + return (channelConfiguration.size() > 0 ? channelConfiguration[0].numIns + : processor->getMainBusNumInputChannels()); + } + + int getNumOutputChannels() const + { + if (processor == nullptr) + return 0; + + return (channelConfiguration.size() > 0 ? channelConfiguration[0].numOuts + : processor->getMainBusNumOutputChannels()); + } + static String getFilePatterns (const String& fileSuffix) { if (fileSuffix.isEmpty()) @@ -179,11 +191,18 @@ public: /** Pops up a dialog letting the user save the processor's state to a file. */ void askUserToSaveState (const String& fileSuffix = String()) { - #if JUCE_MODAL_LOOPS_PERMITTED - FileChooser fc (TRANS("Save current state"), getLastFile(), getFilePatterns (fileSuffix)); + stateFileChooser = std::make_unique (TRANS("Save current state"), + getLastFile(), + getFilePatterns (fileSuffix)); + auto flags = FileBrowserComponent::saveMode + | FileBrowserComponent::canSelectFiles + | FileBrowserComponent::warnAboutOverwriting; - if (fc.browseForFileToSave (true)) + stateFileChooser->launchAsync (flags, [this] (const FileChooser& fc) { + if (fc.getResult() == File{}) + return; + setLastFile (fc); MemoryBlock data; @@ -193,20 +212,23 @@ public: AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, TRANS("Error whilst saving"), TRANS("Couldn't write to the specified file!")); - } - #else - ignoreUnused (fileSuffix); - #endif + }); } /** Pops up a dialog letting the user re-load the processor's state from a file. */ void askUserToLoadState (const String& fileSuffix = String()) { - #if JUCE_MODAL_LOOPS_PERMITTED - FileChooser fc (TRANS("Load a saved state"), getLastFile(), getFilePatterns (fileSuffix)); + stateFileChooser = std::make_unique (TRANS("Load a saved state"), + getLastFile(), + getFilePatterns (fileSuffix)); + auto flags = FileBrowserComponent::openMode + | FileBrowserComponent::canSelectFiles; - if (fc.browseForFileToOpen()) + stateFileChooser->launchAsync (flags, [this] (const FileChooser& fc) { + if (fc.getResult() == File{}) + return; + setLastFile (fc); MemoryBlock data; @@ -217,10 +239,7 @@ public: AlertWindow::showMessageBoxAsync (AlertWindow::WarningIcon, TRANS("Error whilst loading"), TRANS("Couldn't read from the specified file!")); - } - #else - ignoreUnused (fileSuffix); - #endif + }); } //============================================================================== @@ -264,15 +283,11 @@ public: if (auto* bus = processor->getBus (false, 0)) maxNumOutputs = jmax (0, bus->getDefaultLayout().size()); - o.content.setOwned (new SettingsComponent (*this, deviceManager, maxNumInputs, maxNumOutputs)); - - LookAndFeel_V4 *lfv4 = dynamic_cast(&o.content->getLookAndFeel()); - - if (lfv4) { - lfv4->setColour(ResizableWindow::backgroundColourId, Colour::fromHSV (0.0f, 0.0f, 0.1f, 1.0f)); - } - - o.content->setSize (500, 400); + auto content = std::make_unique (*this, deviceManager, maxNumInputs, maxNumOutputs); + content->setSize (500, 550); + content->setToRecommendedSize(); + + o.content.setOwned (content.release()); o.dialogTitle = TRANS("Audio/MIDI Settings"); o.dialogBackgroundColour = o.content->getLookAndFeel().findColour (ResizableWindow::backgroundColourId); @@ -312,18 +327,17 @@ public: #endif } - auto totalInChannels = processor->getMainBusNumInputChannels(); - auto totalOutChannels = processor->getMainBusNumOutputChannels(); + auto inputChannels = getNumInputChannels(); + auto outputChannels = getNumOutputChannels(); - if (channelConfiguration.size() > 0) + if (inputChannels == 0 && outputChannels == 0 && processor->isMidiEffect()) { - auto defaultConfig = channelConfiguration.getReference (0); - totalInChannels = defaultConfig.numIns; - totalOutChannels = defaultConfig.numOuts; + // add a dummy output channel for MIDI effect plug-ins so they can receive audio callbacks + outputChannels = 1; } - deviceManager.initialise (enableAudioInput ? totalInChannels : 0, - totalOutChannels, + deviceManager.initialise (enableAudioInput ? inputChannels : 0, + outputChannels, savedState.get(), true, preferredDefaultDeviceName, @@ -403,7 +417,93 @@ public: std::unique_ptr options; Array lastMidiDevices; + std::unique_ptr stateFileChooser; + private: + /* This class can be used to ensure that audio callbacks use buffers with a + predictable maximum size. + + On some platforms (such as iOS 10), the expected buffer size reported in + audioDeviceAboutToStart may be smaller than the blocks passed to + audioDeviceIOCallback. This can lead to out-of-bounds reads if the render + callback depends on additional buffers which were initialised using the + smaller size. + + As a workaround, this class will ensure that the render callback will + only ever be called with a block with a length less than or equal to the + expected block size. + */ + class CallbackMaxSizeEnforcer : public AudioIODeviceCallback + { + public: + explicit CallbackMaxSizeEnforcer (AudioIODeviceCallback& callbackIn) + : inner (callbackIn) {} + + void audioDeviceAboutToStart (AudioIODevice* device) override + { + maximumSize = device->getCurrentBufferSizeSamples(); + storedInputChannels .resize ((size_t) device->getActiveInputChannels() .countNumberOfSetBits()); + storedOutputChannels.resize ((size_t) device->getActiveOutputChannels().countNumberOfSetBits()); + + inner.audioDeviceAboutToStart (device); + } + + void audioDeviceIOCallback (const float** inputChannelData, + int numInputChannels, + float** outputChannelData, + int numOutputChannels, + int numSamples) override + { + jassertquiet ((int) storedInputChannels.size() == numInputChannels); + jassertquiet ((int) storedOutputChannels.size() == numOutputChannels); + + int position = 0; + + while (position < numSamples) + { + const auto blockLength = jmin (maximumSize, numSamples - position); + + initChannelPointers (inputChannelData, storedInputChannels, position); + initChannelPointers (outputChannelData, storedOutputChannels, position); + + inner.audioDeviceIOCallback (storedInputChannels.data(), + (int) storedInputChannels.size(), + storedOutputChannels.data(), + (int) storedOutputChannels.size(), + blockLength); + + position += blockLength; + } + } + + void audioDeviceStopped() override + { + inner.audioDeviceStopped(); + } + + private: + struct GetChannelWithOffset + { + int offset; + + template + auto operator() (Ptr ptr) const noexcept -> Ptr { return ptr + offset; } + }; + + template + void initChannelPointers (Ptr&& source, Vector&& target, int offset) + { + std::transform (source, source + target.size(), target.begin(), GetChannelWithOffset { offset }); + } + + AudioIODeviceCallback& inner; + int maximumSize = 0; + std::vector storedInputChannels; + std::vector storedOutputChannels; + }; + + CallbackMaxSizeEnforcer maxSizeEnforcer { *this }; + //============================================================================== class SettingsComponent : public Component { @@ -414,7 +514,7 @@ private: int maxAudioOutputChannels) : owner (pluginHolder), deviceSelector (deviceManagerToUse, - 0, 0, // maxAudioInputChannels, // force zero to fully disable input selection + 0, maxAudioInputChannels, 0, maxAudioOutputChannels, true, (pluginHolder.processor.get() != nullptr && pluginHolder.processor->producesMidi()), @@ -436,17 +536,6 @@ private: shouldMuteLabel.attachToComponent (&shouldMuteButton, true); } - - for (int i =0; i < deviceSelector.getNumChildComponents(); i ++){ - - LookAndFeel_V4 *lfv4 = dynamic_cast(&deviceSelector.getChildComponent(i) ->getLookAndFeel()); - if (lfv4) { - lfv4->setColour(ComboBox::ColourIds::backgroundColourId, Colour::fromHSV (0.0f, 0.0f, 0.2f, 1.0f)); - lfv4->setColour(ListBox::ColourIds::backgroundColourId, Colour::fromHSV (0.0f, 0.0f, 0.2f, 1.0f)); - lfv4->setColour(TextButton::ColourIds::buttonColourId, Colour::fromHSV (0.0f, 0.0f, 0.1f, 1.0f)); - - } - } } void paint (Graphics& g) override @@ -456,6 +545,8 @@ private: void resized() override { + const ScopedValueSetter scope (isResizing, true); + auto r = getLocalBounds(); if (owner.getProcessorHasPotentialFeedbackLoop()) @@ -473,12 +564,34 @@ private: deviceSelector.setBounds (r); } + void childBoundsChanged (Component* childComp) override + { + if (! isResizing && childComp == &deviceSelector) + setToRecommendedSize(); + } + + void setToRecommendedSize() + { + const auto extraHeight = [&] + { + if (! owner.getProcessorHasPotentialFeedbackLoop()) + return 0; + + const auto itemHeight = deviceSelector.getItemHeight(); + const auto separatorHeight = (itemHeight >> 1); + return itemHeight + separatorHeight; + }(); + + setSize (getWidth(), deviceSelector.getHeight() + extraHeight); + } + private: //============================================================================== StandalonePluginHolder& owner; AudioDeviceSelectorComponent deviceSelector; Label shouldMuteLabel; ToggleButton shouldMuteButton; + bool isResizing = false; //============================================================================== JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SettingsComponent) @@ -522,7 +635,7 @@ private: const String& preferredDefaultDeviceName, const AudioDeviceManager::AudioDeviceSetup* preferredSetupOptions) { - deviceManager.addAudioCallback (this); + deviceManager.addAudioCallback (&maxSizeEnforcer); deviceManager.addMidiInputDeviceCallback ({}, &player); reloadAudioDeviceState (enableAudioInput, preferredDefaultDeviceName, preferredSetupOptions); @@ -533,7 +646,7 @@ private: saveAudioDeviceState(); deviceManager.removeMidiInputDeviceCallback ({}, &player); - deviceManager.removeAudioCallback (this); + deviceManager.removeAudioCallback (&maxSizeEnforcer); } void timerCallback() override @@ -568,8 +681,9 @@ private: @tags{Audio} */ class StandaloneFilterWindow : public DocumentWindow, - public Button::Listener, + private Button::Listener, public MenuBarModel + { public: //============================================================================== @@ -588,46 +702,46 @@ public: const String& preferredDefaultDeviceName = String(), const AudioDeviceManager::AudioDeviceSetup* preferredSetupOptions = nullptr, const Array& constrainToConfiguration = {}, - #if JUCE_ANDROID || JUCE_IOS - bool autoOpenMidiDevices = true - #else - bool autoOpenMidiDevices = false - #endif - ) +#if JUCE_ANDROID || JUCE_IOS + bool autoOpenMidiDevices = true +#else + bool autoOpenMidiDevices = true +#endif + ) #if JUCE_MAC - : DocumentWindow ("", backgroundColour, DocumentWindow::minimiseButton | DocumentWindow::closeButton), + : DocumentWindow("", backgroundColour, DocumentWindow::minimiseButton | DocumentWindow::closeButton), #endif #if ! JUCE_MAC - : DocumentWindow (title, Colour::fromHSV (0.0f, 0.0f, 0.1f, 0.5f), DocumentWindow::minimiseButton | DocumentWindow::closeButton), + : DocumentWindow(title, juce::Colours::black, DocumentWindow::minimiseButton | DocumentWindow::closeButton), #endif - + menuBar(this) - #if ! JUCE_MAC - , optionsButton ("Settings") - #endif +#if ! JUCE_MAC + , optionsButton("Settings") +#endif { - #if JUCE_IOS || JUCE_ANDROID - setTitleBarHeight (0); - #else - - LookAndFeel_V4 *lfv4 = dynamic_cast(&getLookAndFeel()); -if (lfv4) { -lfv4->getCurrentColourScheme().setUIColour (LookAndFeel_V4::ColourScheme::widgetBackground, Colour::fromHSV (0.0f, 0.0f, 0.1f, 1.0f)); -} - - setTitleBarButtonsRequired (DocumentWindow::minimiseButton | DocumentWindow::closeButton, false); - - #if JUCE_MAC +#if JUCE_IOS || JUCE_ANDROID + setTitleBarHeight(0); +#else + + LookAndFeel_V4* lfv4 = dynamic_cast(&getLookAndFeel()); + if (lfv4) { + lfv4->getCurrentColourScheme().setUIColour(LookAndFeel_V4::ColourScheme::widgetBackground, Colour::fromHSV(0.0f, 0.0f, 0.1f, 1.0f)); + } + + setTitleBarButtonsRequired(DocumentWindow::minimiseButton | DocumentWindow::closeButton, false); + +#if JUCE_MAC setUsingNativeTitleBar(true); - menu.addItem (1, TRANS("Audio/MIDI Settings...")); - MenuBarModel::setMacMainMenu (this, &menu); - #else - Component::addAndMakeVisible (optionsButton); - optionsButton.addListener (this); - optionsButton.setTriggeredOnMouseDown (true); - #endif - #endif + menu.addItem(1, TRANS("Audio/MIDI Settings...")); + MenuBarModel::setMacMainMenu(this, &menu); +#else + Component::addAndMakeVisible(optionsButton); + optionsButton.addListener(this); + optionsButton.setTriggeredOnMouseDown(true); +#endif +#endif pluginHolder.reset (new StandalonePluginHolder (settingsToUse, takeOwnershipOfSettings, preferredDefaultDeviceName, preferredSetupOptions, @@ -635,32 +749,55 @@ lfv4->getCurrentColourScheme().setUIColour (LookAndFeel_V4::ColourScheme::widget #if JUCE_IOS || JUCE_ANDROID setFullScreen (true); - setContentOwned (new MainContentComponent (*this), false); + updateContent(); #else - setContentOwned (new MainContentComponent (*this), true); + updateContent(); - if (auto* props = pluginHolder->settings.get()) + const auto windowScreenBounds = [this]() -> Rectangle { - const int x = props->getIntValue ("windowX", -100); - const int y = props->getIntValue ("windowY", -100); + const auto width = getWidth(); + const auto height = getHeight(); - if (x != -100 && y != -100) - setBoundsConstrained ({ x, y, getWidth(), getHeight() }); - else - centreWithSize (getWidth(), getHeight()); - } - else - { - centreWithSize (getWidth(), getHeight()); - } + const auto& displays = Desktop::getInstance().getDisplays(); + + if (auto* props = pluginHolder->settings.get()) + { + constexpr int defaultValue = -100; + + const auto x = props->getIntValue ("windowX", defaultValue); + const auto y = props->getIntValue ("windowY", defaultValue); + + if (x != defaultValue && y != defaultValue) + { + const auto screenLimits = displays.getDisplayForRect ({ x, y, width, height })->userArea; + + return { jlimit (screenLimits.getX(), jmax (screenLimits.getX(), screenLimits.getRight() - width), x), + jlimit (screenLimits.getY(), jmax (screenLimits.getY(), screenLimits.getBottom() - height), y), + width, height }; + } + } + + const auto displayArea = displays.getPrimaryDisplay()->userArea; + + return { displayArea.getCentreX() - width / 2, + displayArea.getCentreY() - height / 2, + width, height }; + }(); + + setBoundsConstrained (windowScreenBounds); + + if (auto* processor = getAudioProcessor()) + if (auto* editor = processor->getActiveEditor()) + setResizable (editor->isResizable(), false); #endif } ~StandaloneFilterWindow() override { - #if JUCE_MAC - setMacMainMenu(nullptr); - #endif + #if JUCE_MAC + MenuBarModel::setMacMainMenu(nullptr); + #endif + #if (! JUCE_IOS) && (! JUCE_ANDROID) if (auto* props = pluginHolder->settings.get()) { @@ -689,7 +826,7 @@ lfv4->getCurrentColourScheme().setUIColour (LookAndFeel_V4::ColourScheme::widget props->removeValue ("filterState"); pluginHolder->createPlugin(); - setContentOwned (new MainContentComponent (*this), true); + updateContent(); pluginHolder->startPlaying(); } @@ -703,31 +840,31 @@ lfv4->getCurrentColourScheme().setUIColour (LookAndFeel_V4::ColourScheme::widget StringArray getMenuBarNames() override { -// StringArray menuBarNames; -// menuBarNames.add("Options"); -// return menuBarNames; - const char *menuNames[] = { 0 }; - - return StringArray (menuNames); + // StringArray menuBarNames; + // menuBarNames.add("Options"); + // return menuBarNames; + const char* menuNames[] = { 0 }; + + return StringArray(menuNames); } - PopupMenu getMenuForIndex (int topLevelMenuIndex, const String& menuName) override + PopupMenu getMenuForIndex(int topLevelMenuIndex, const String& menuName) override { PopupMenu m; -// m.addItem (1, TRANS("Audio Settings...")); -// m.addSeparator(); + // m.addItem (1, TRANS("Audio Settings...")); + // m.addSeparator(); return m; } - void menuItemSelected (int menuItemID, int topLevelMenuIndex) override + void menuItemSelected(int menuItemID, int topLevelMenuIndex) override { handleMenuResult(menuItemID); } - void menuBarActivated (bool isActive) override {}; + void menuBarActivated(bool isActive) override {}; + - void handleMenuResult (int result) { @@ -750,9 +887,7 @@ lfv4->getCurrentColourScheme().setUIColour (LookAndFeel_V4::ColourScheme::widget void resized() override { DocumentWindow::resized(); - #if ! JUCE_MAC optionsButton.setBounds (8, 6, 60, getTitleBarHeight() - 8); - #endif } virtual StandalonePluginHolder* getPluginHolder() { return pluginHolder.get(); } @@ -762,20 +897,23 @@ lfv4->getCurrentColourScheme().setUIColour (LookAndFeel_V4::ColourScheme::widget PopupMenu menu; private: + void updateContent() + { + auto* content = new MainContentComponent (*this); + decoratorConstrainer.setMainContentComponent (content); + + #if JUCE_IOS || JUCE_ANDROID + constexpr auto resizeAutomatically = false; + #else + constexpr auto resizeAutomatically = true; + #endif + + setContentOwned (content, resizeAutomatically); + } + void buttonClicked (Button*) override { pluginHolder->showAudioSettingsDialog(); - - //PopupMenu m; - //m.addItem (1, TRANS("Audio/MIDI Settings...")); - //m.addSeparator(); - //m.addItem (2, TRANS("Save current state...")); - //m.addItem (3, TRANS("Load a saved state...")); - //m.addSeparator(); - //m.addItem (4, TRANS("Reset to default state")); - - //m.showMenuAsync (PopupMenu::Options(), - // ModalCallbackFunction::forComponent (menuCallback, this)); } //============================================================================== @@ -790,7 +928,7 @@ private: editor (owner.getAudioProcessor()->hasEditor() ? owner.getAudioProcessor()->createEditorIfNeeded() : new GenericAudioProcessorEditor (*owner.getAudioProcessor())) { - Value& inputMutedValue = owner.pluginHolder->getMuteInputValue(); + inputMutedValue.referTo (owner.pluginHolder->getMuteInputValue()); if (editor != nullptr) { @@ -829,9 +967,31 @@ private: notification.setBounds (r.removeFromTop (NotificationArea::height)); if (editor != nullptr) - editor->setBounds (editor->getLocalArea (this, r.toFloat()) - .withPosition (r.getTopLeft().toFloat().transformedBy (editor->getTransform().inverted())) - .toNearestInt()); + { + const auto newPos = r.getTopLeft().toFloat().transformedBy (editor->getTransform().inverted()); + + if (preventResizingEditor) + editor->setTopLeftPosition (newPos.roundToInt()); + else + editor->setBoundsConstrained (editor->getLocalArea (this, r.toFloat()).withPosition (newPos).toNearestInt()); + } + } + + ComponentBoundsConstrainer* getEditorConstrainer() const + { + if (auto* e = editor.get()) + return e->getConstrainer(); + + return nullptr; + } + + BorderSize computeBorder() const + { + const auto outer = owner.getContentComponentBorder(); + return { outer.getTop() + (shouldShowNotification ? NotificationArea::height : 0), + outer.getLeft(), + outer.getBottom(), + outer.getRight() }; } private: @@ -846,7 +1006,7 @@ private: #if JUCE_IOS || JUCE_ANDROID settingsButton ("Unmute Input") #else - settingsButton ("Settings") + settingsButton ("Settings...") #endif { setOpaque (true); @@ -893,10 +1053,9 @@ private: #else if (editor != nullptr) { - auto rect = getSizeToContainEditor(); - - setSize (rect.getWidth(), - rect.getHeight() + (shouldShowNotification ? NotificationArea::height : 0)); + const int extraHeight = shouldShowNotification ? NotificationArea::height : 0; + const auto rect = getSizeToContainEditor(); + setSize (rect.getWidth(), rect.getHeight() + extraHeight); } #endif } @@ -914,6 +1073,8 @@ private: //============================================================================== void componentMovedOrResized (Component&, bool, bool) override { + const ScopedValueSetter scope (preventResizingEditor, true); + if (editor != nullptr) { auto rect = getSizeToContainEditor(); @@ -935,15 +1096,87 @@ private: StandaloneFilterWindow& owner; NotificationArea notification; std::unique_ptr editor; + Value inputMutedValue; bool shouldShowNotification = false; + bool preventResizingEditor = false; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MainContentComponent) }; + /* This custom constrainer checks with the AudioProcessorEditor (which might itself be + constrained) to ensure that any size we choose for the standalone window will be suitable + for the editor too. + + Without this constrainer, attempting to resize the standalone window may set bounds on the + peer that are unsupported by the inner editor. In this scenario, the peer will be set to a + 'bad' size, then the inner editor will be resized. The editor will check the new bounds with + its own constrainer, and may set itself to a more suitable size. After that, the resizable + window will see that its content component has changed size, and set the bounds of the peer + accordingly. The end result is that the peer is resized twice in a row to different sizes, + which can appear glitchy/flickery to the user. + */ + struct DecoratorConstrainer : public ComponentBoundsConstrainer + { + void checkBounds (Rectangle& bounds, + const Rectangle& previousBounds, + const Rectangle& limits, + bool isStretchingTop, + bool isStretchingLeft, + bool isStretchingBottom, + bool isStretchingRight) override + { + auto* decorated = contentComponent != nullptr ? contentComponent->getEditorConstrainer() + : nullptr; + + if (decorated != nullptr) + { + const auto border = contentComponent->computeBorder(); + const auto requestedBounds = bounds; + + border.subtractFrom (bounds); + decorated->checkBounds (bounds, + border.subtractedFrom (previousBounds), + limits, + isStretchingTop, + isStretchingLeft, + isStretchingBottom, + isStretchingRight); + border.addTo (bounds); + bounds = bounds.withPosition (requestedBounds.getPosition()); + + if (isStretchingTop && ! isStretchingBottom) + bounds = bounds.withBottomY (previousBounds.getBottom()); + + if (! isStretchingTop && isStretchingBottom) + bounds = bounds.withY (previousBounds.getY()); + + if (isStretchingLeft && ! isStretchingRight) + bounds = bounds.withRightX (previousBounds.getRight()); + + if (! isStretchingLeft && isStretchingRight) + bounds = bounds.withX (previousBounds.getX()); + } + else + { + ComponentBoundsConstrainer::checkBounds (bounds, + previousBounds, + limits, + isStretchingTop, + isStretchingLeft, + isStretchingBottom, + isStretchingRight); + } + } + + void setMainContentComponent (MainContentComponent* in) { contentComponent = in; } + + private: + MainContentComponent* contentComponent = nullptr; + }; + //============================================================================== - #if ! JUCE_MAC TextButton optionsButton; - #endif + DecoratorConstrainer decoratorConstrainer; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (StandaloneFilterWindow) }; diff --git a/OB-Xd.jucer b/OB-Xd.jucer index ebf2e6d..966e620 100644 --- a/OB-Xd.jucer +++ b/OB-Xd.jucer @@ -9,11 +9,12 @@ pluginAUExportPrefix="" pluginRTASCategory="2048" aaxIdentifier="com.discodsp.obxd" companyName="discoDSP" companyWebsite="https://www.discodsp.com/" pluginIsMidiEffectPlugin="0" pluginCharacteristicsValue="pluginIsSynth,pluginWantsMidiIn" - pluginFormats="buildAU,buildStandalone,buildVST,buildVST3" buildVST="1" - buildVST3="1" buildAU="1" buildAUv3="0" buildRTAS="0" buildAAX="0" - buildStandalone="1" enableIAA="0" jucerFormatVersion="1" pluginChannelConfigs="{0,2}" + pluginFormats="buildStandalone,buildVST3" buildVST="0" buildVST3="1" + buildAU="0" buildAUv3="0" buildRTAS="0" buildAAX="0" buildStandalone="1" + enableIAA="0" jucerFormatVersion="1" pluginChannelConfigs="{0,2}" companyCopyright="discoDSP" companyEmail="contactus@discodsp.com" - pluginAAXCategory="2048" pluginVSTCategory="kPlugCategSynth"> + pluginAAXCategory="2048" pluginVSTCategory="kPlugCategSynth" + displaySplashScreen="1"> @@ -91,7 +92,7 @@ + aaxFolder="Modules/aax" extraDefs="JUCE_MODAL_LOOPS_PERMITTED=1"> @@ -116,7 +117,7 @@ + vstLegacyFolder="Modules/vstsdk2.4" extraDefs="JUCE_MODAL_LOOPS_PERMITTED=1"> + aaxFolder="Modules/aax" extraDefs="JUCE_MODAL_LOOPS_PERMITTED=1"> diff --git a/README.md b/README.md index 7751f60..e9e8732 100644 --- a/README.md +++ b/README.md @@ -15,4 +15,4 @@ Latest binaries can be downloaded at https://www.discodsp.com/obxd/ # Building -Source code can be compiled with [JUCE 6.0.5](https://github.com/juce-framework/JUCE/releases/tag/6.0.5) and VST3 SDK. +Source code can be compiled with [JUCE 6.1.6](https://github.com/juce-framework/JUCE/releases/tag/6.1.6) and VST3 SDK. diff --git a/Source/Gui/Knob.h b/Source/Gui/Knob.h index fbced57..d0cf5ba 100644 --- a/Source/Gui/Knob.h +++ b/Source/Gui/Knob.h @@ -61,7 +61,6 @@ public: h2 = fh; w2 = kni.getWidth(); numFr = kni.getHeight() / h2; - setPopupDisplayEnabled(true, true, getParentComponent()); setLookAndFeel(&lookAndFeel); } diff --git a/Source/PluginEditor.cpp b/Source/PluginEditor.cpp index 3085ce1..176890b 100755 --- a/Source/PluginEditor.cpp +++ b/Source/PluginEditor.cpp @@ -122,8 +122,15 @@ void ObxdAudioProcessorEditor::resized() { if (mappingComps[name] != nullptr){ if (auto* knob = dynamic_cast(mappingComps[name])){ knob->setBounds(transformBounds(x, y, d,d)); - if (!tooltipEnabled) + const auto tooltipBehavior = processor.getTooltipBehavior(); + if (tooltipBehavior == Tooltip::Disable || + (tooltipBehavior == Tooltip::StandardDisplay && !tooltipEnabled)) + { knob->setPopupDisplayEnabled(false, false, nullptr); + } else + { + knob->setPopupDisplayEnabled(true, true, knob->getParentComponent()); + } } else if (dynamic_cast(mappingComps[name])){ mappingComps[name]->setBounds(transformBounds(x, y, w, h)); @@ -954,6 +961,24 @@ void ObxdAudioProcessorEditor::createMenu () scaleMenu.addItem(menuScaleNum+2, "2x", true, getScaleFactor() == 2.0f); menu->addSubMenu("GUI Size", scaleMenu, true); + PopupMenu tooltipMenu; + tooltipMenu.addItem("Disabled", true, processor.getTooltipBehavior() == Tooltip::Disable, [&] + { + processor.setTooltipBehavior(Tooltip::Disable); + resized(); + }); + tooltipMenu.addItem("Standard Display", true, processor.getTooltipBehavior() == Tooltip::StandardDisplay, [&] + { + processor.setTooltipBehavior(Tooltip::StandardDisplay); + resized(); + }); + tooltipMenu.addItem("Full Display", true, processor.getTooltipBehavior() == Tooltip::FullDisplay, [&] + { + processor.setTooltipBehavior(Tooltip::FullDisplay); + resized(); + }); + menu->addSubMenu("Tooltips", tooltipMenu, true); + #ifdef LINUX menu->addItem(1, String("Release ") + String(JucePlugin_VersionString).dropLastCharacters(2), false); #endif diff --git a/Source/PluginProcessor.cpp b/Source/PluginProcessor.cpp index 987c565..50115bf 100644 --- a/Source/PluginProcessor.cpp +++ b/Source/PluginProcessor.cpp @@ -74,6 +74,7 @@ ObxdAudioProcessor::ObxdAudioProcessor() config = std::unique_ptr (new PropertiesFile (getDocumentFolder().getChildFile ("Skin.xml"), options)); showPresetBar = config->getBoolValue("presetnavigation"); gui_size = config->getIntValue("gui_size", 1); + tooltipBehavior = static_cast(config->getIntValue("tooltip", 1)); currentSkin = config->containsKey("skin") ? config->getValue("skin") : "Ilkka Rosma Dark"; currentBank = "000 - FMR OB-Xa Patch Book"; @@ -824,6 +825,19 @@ void ObxdAudioProcessor::setGuiSize(const int gui_size) { config->setValue("gui_size", gui_size); config->setNeedsToBeSaved(true); } + +Tooltip ObxdAudioProcessor::getTooltipBehavior() const +{ + return tooltipBehavior; +} + +void ObxdAudioProcessor::setTooltipBehavior(const Tooltip tooltip) +{ + this->tooltipBehavior = tooltip; + config->setValue("tooltip", static_cast(tooltip)); + config->setNeedsToBeSaved(true); +} + //============================================================================== String ObxdAudioProcessor::getEngineParameterId (size_t index) { @@ -1017,7 +1031,7 @@ String ObxdAudioProcessor::getTrueParameterValueFromNormalizedRange(size_t index break; } - return String{ value, 2 }; + return String{ static_cast(jmap(value, 0.f, 127.f)) }; } int ObxdAudioProcessor::getParameterIndexFromId (String paramId) diff --git a/Source/PluginProcessor.h b/Source/PluginProcessor.h index 0656600..127dbf8 100644 --- a/Source/PluginProcessor.h +++ b/Source/PluginProcessor.h @@ -110,6 +110,12 @@ static inline float fxbSwapFloat (const float x) noexcept #endif } +enum class Tooltip +{ + Disable = 0, + StandardDisplay, + FullDisplay +}; //============================================================================== /** */ @@ -196,6 +202,8 @@ public: File getCurrentSkinFolder() const; void setCurrentSkinFolder(const String& folderName); void setGuiSize(const int gui_size); + Tooltip getTooltipBehavior() const; + void setTooltipBehavior(const Tooltip tooltip); //============================================================================== static String getEngineParameterId (size_t); static String getTrueParameterValueFromNormalizedRange(size_t, float normalizedValue); @@ -251,6 +259,7 @@ public: private: Array bankFiles; Array skinFiles; + Tooltip tooltipBehavior; std::unique_ptr config; InterProcessLock configLock;