6.1.6 framework update and tooltip enhancements
This commit is contained in:
parent
29bce70503
commit
5adcad4a7a
7 changed files with 432 additions and 151 deletions
|
@ -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<FileChooser> (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<FileChooser> (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));
|
||||
auto content = std::make_unique<SettingsComponent> (*this, deviceManager, maxNumInputs, maxNumOutputs);
|
||||
content->setSize (500, 550);
|
||||
content->setToRecommendedSize();
|
||||
|
||||
LookAndFeel_V4 *lfv4 = dynamic_cast<LookAndFeel_V4*>(&o.content->getLookAndFeel());
|
||||
|
||||
if (lfv4) {
|
||||
lfv4->setColour(ResizableWindow::backgroundColourId, Colour::fromHSV (0.0f, 0.0f, 0.1f, 1.0f));
|
||||
}
|
||||
|
||||
o.content->setSize (500, 400);
|
||||
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<AudioDeviceManager::AudioDeviceSetup> options;
|
||||
Array<MidiDeviceInfo> lastMidiDevices;
|
||||
|
||||
std::unique_ptr<FileChooser> 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 <typename Ptr>
|
||||
auto operator() (Ptr ptr) const noexcept -> Ptr { return ptr + offset; }
|
||||
};
|
||||
|
||||
template <typename Ptr, typename Vector>
|
||||
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<const float*> storedInputChannels;
|
||||
std::vector<float*> 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<LookAndFeel_V4*>(&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<bool> 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<PluginInOuts>& constrainToConfiguration = {},
|
||||
#if JUCE_ANDROID || JUCE_IOS
|
||||
#if JUCE_ANDROID || JUCE_IOS
|
||||
bool autoOpenMidiDevices = true
|
||||
#else
|
||||
bool autoOpenMidiDevices = false
|
||||
#endif
|
||||
#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
|
||||
#if JUCE_IOS || JUCE_ANDROID
|
||||
setTitleBarHeight(0);
|
||||
#else
|
||||
|
||||
LookAndFeel_V4 *lfv4 = dynamic_cast<LookAndFeel_V4*>(&getLookAndFeel());
|
||||
if (lfv4) {
|
||||
lfv4->getCurrentColourScheme().setUIColour (LookAndFeel_V4::ColourScheme::widgetBackground, Colour::fromHSV (0.0f, 0.0f, 0.1f, 1.0f));
|
||||
}
|
||||
LookAndFeel_V4* lfv4 = dynamic_cast<LookAndFeel_V4*>(&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);
|
||||
setTitleBarButtonsRequired(DocumentWindow::minimiseButton | DocumentWindow::closeButton, false);
|
||||
|
||||
#if JUCE_MAC
|
||||
#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();
|
||||
|
||||
const auto windowScreenBounds = [this]() -> Rectangle<int>
|
||||
{
|
||||
const auto width = getWidth();
|
||||
const auto height = getHeight();
|
||||
|
||||
const auto& displays = Desktop::getInstance().getDisplays();
|
||||
|
||||
if (auto* props = pluginHolder->settings.get())
|
||||
{
|
||||
const int x = props->getIntValue ("windowX", -100);
|
||||
const int y = props->getIntValue ("windowY", -100);
|
||||
constexpr int defaultValue = -100;
|
||||
|
||||
if (x != -100 && y != -100)
|
||||
setBoundsConstrained ({ x, y, getWidth(), getHeight() });
|
||||
else
|
||||
centreWithSize (getWidth(), getHeight());
|
||||
}
|
||||
else
|
||||
const auto x = props->getIntValue ("windowX", defaultValue);
|
||||
const auto y = props->getIntValue ("windowY", defaultValue);
|
||||
|
||||
if (x != defaultValue && y != defaultValue)
|
||||
{
|
||||
centreWithSize (getWidth(), getHeight());
|
||||
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);
|
||||
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,29 +840,29 @@ lfv4->getCurrentColourScheme().setUIColour (LookAndFeel_V4::ColourScheme::widget
|
|||
|
||||
StringArray getMenuBarNames() override
|
||||
{
|
||||
// StringArray menuBarNames;
|
||||
// menuBarNames.add("Options");
|
||||
// return menuBarNames;
|
||||
const char *menuNames[] = { 0 };
|
||||
// StringArray menuBarNames;
|
||||
// menuBarNames.add("Options");
|
||||
// return menuBarNames;
|
||||
const char* menuNames[] = { 0 };
|
||||
|
||||
return StringArray (menuNames);
|
||||
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 {};
|
||||
|
||||
|
||||
|
||||
|
@ -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<int> 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<bool> scope (preventResizingEditor, true);
|
||||
|
||||
if (editor != nullptr)
|
||||
{
|
||||
auto rect = getSizeToContainEditor();
|
||||
|
@ -935,15 +1096,87 @@ private:
|
|||
StandaloneFilterWindow& owner;
|
||||
NotificationArea notification;
|
||||
std::unique_ptr<AudioProcessorEditor> 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<int>& bounds,
|
||||
const Rectangle<int>& previousBounds,
|
||||
const Rectangle<int>& 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)
|
||||
};
|
||||
|
|
15
OB-Xd.jucer
15
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">
|
||||
<MAINGROUP id="NZ3n4V" name="OB-Xd">
|
||||
<GROUP id="{90740217-84AB-FD0D-FBC4-CA9EA2C68D5E}" name="Source">
|
||||
<GROUP id="{E11C29DD-69D5-DA26-5CFF-B65751876DEE}" name="MTS">
|
||||
|
@ -91,7 +92,7 @@
|
|||
<EXPORTFORMATS>
|
||||
<XCODE_MAC targetFolder="Builds/MacOSX" bigIcon="nnY63W" smallIcon="nnY63W"
|
||||
vstLegacyFolder="Modules/vstsdk2.4" vst3Folder="Modules/vstsdk3"
|
||||
aaxFolder="Modules/aax">
|
||||
aaxFolder="Modules/aax" extraDefs="JUCE_MODAL_LOOPS_PERMITTED=1">
|
||||
<CONFIGURATIONS>
|
||||
<CONFIGURATION name="Debug" isDebug="1" optimisation="1" targetName="OB-Xd"
|
||||
stripLocalSymbols="0" linkTimeOptimisation="0"/>
|
||||
|
@ -116,7 +117,7 @@
|
|||
</MODULEPATHS>
|
||||
</XCODE_MAC>
|
||||
<LINUX_MAKE targetFolder="Builds/LinuxMakefile" extraLinkerFlags="-no-pie"
|
||||
vstLegacyFolder="Modules/vstsdk2.4">
|
||||
vstLegacyFolder="Modules/vstsdk2.4" extraDefs="JUCE_MODAL_LOOPS_PERMITTED=1">
|
||||
<CONFIGURATIONS>
|
||||
<CONFIGURATION name="Release64" libraryPath="/usr/X11R6/lib/" isDebug="0" optimisation="3"
|
||||
targetName="OB-Xd" linuxArchitecture="-m64" headerPath="../../JuceLibraryCode ../../Source ../Modules/vstsdk2.4 /usr/include/freetype2 /usr/include"
|
||||
|
@ -140,7 +141,7 @@
|
|||
</LINUX_MAKE>
|
||||
<VS2019 targetFolder="Builds/VisualStudio2019" smallIcon="nnY63W" bigIcon="nnY63W"
|
||||
vstLegacyFolder="Modules/vstsdk2.4" vst3Folder="Modules/vstsdk3"
|
||||
aaxFolder="Modules/aax">
|
||||
aaxFolder="Modules/aax" extraDefs="JUCE_MODAL_LOOPS_PERMITTED=1">
|
||||
<CONFIGURATIONS>
|
||||
<CONFIGURATION isDebug="1" name="Debug" targetName="OB-Xd" headerPath="../../Modules/asiosdk2.3.2/common"
|
||||
useRuntimeLibDLL="0" enablePluginBinaryCopyStep="1"/>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -61,7 +61,6 @@ public:
|
|||
h2 = fh;
|
||||
w2 = kni.getWidth();
|
||||
numFr = kni.getHeight() / h2;
|
||||
setPopupDisplayEnabled(true, true, getParentComponent());
|
||||
setLookAndFeel(&lookAndFeel);
|
||||
}
|
||||
|
||||
|
|
|
@ -122,8 +122,15 @@ void ObxdAudioProcessorEditor::resized() {
|
|||
if (mappingComps[name] != nullptr){
|
||||
if (auto* knob = dynamic_cast<Knob*>(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<ButtonList*>(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
|
||||
|
|
|
@ -74,6 +74,7 @@ ObxdAudioProcessor::ObxdAudioProcessor()
|
|||
config = std::unique_ptr<PropertiesFile> (new PropertiesFile (getDocumentFolder().getChildFile ("Skin.xml"), options));
|
||||
showPresetBar = config->getBoolValue("presetnavigation");
|
||||
gui_size = config->getIntValue("gui_size", 1);
|
||||
tooltipBehavior = static_cast<Tooltip>(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<int>(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<int>(jmap(value, 0.f, 127.f)) };
|
||||
}
|
||||
|
||||
int ObxdAudioProcessor::getParameterIndexFromId (String paramId)
|
||||
|
|
|
@ -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<File> bankFiles;
|
||||
Array<File> skinFiles;
|
||||
Tooltip tooltipBehavior;
|
||||
|
||||
std::unique_ptr<PropertiesFile> config;
|
||||
InterProcessLock configLock;
|
||||
|
|
Loading…
Reference in a new issue