diff --git a/examples/GainPlugin/PluginEditor.cpp b/examples/GainPlugin/PluginEditor.cpp index 898d176..bc4c4f9 100644 --- a/examples/GainPlugin/PluginEditor.cpp +++ b/examples/GainPlugin/PluginEditor.cpp @@ -56,6 +56,11 @@ PluginEditor::PluginEditor(GainPlugin &plug) : juce::AudioProcessorEditor(plug), plugin.getValueTreeState().addParameterListener(gainParameter->paramID, this); setSize(300, 300); + setResizable (true, true); + + constrainer.setSizeLimits (200, 200, 500, 500); + constrainer.setFixedAspectRatio (1.0); + setConstrainer (&constrainer); } PluginEditor::~PluginEditor() diff --git a/examples/GainPlugin/PluginEditor.h b/examples/GainPlugin/PluginEditor.h index 89bc64c..2dc7c4d 100644 --- a/examples/GainPlugin/PluginEditor.h +++ b/examples/GainPlugin/PluginEditor.h @@ -21,5 +21,7 @@ class PluginEditor : public juce::AudioProcessorEditor, std::unique_ptr gainSlider; std::unique_ptr sliderAttachment; + juce::ComponentBoundsConstrainer constrainer; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PluginEditor) }; diff --git a/src/wrapper/clap-juce-wrapper.cpp b/src/wrapper/clap-juce-wrapper.cpp index 589a968..0c1a056 100644 --- a/src/wrapper/clap-juce-wrapper.cpp +++ b/src/wrapper/clap-juce-wrapper.cpp @@ -26,7 +26,8 @@ #include #endif -JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE("-Wunused-parameter", "-Wsign-conversion", "-Wfloat-conversion", "-Wfloat-equal") +JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE("-Wunused-parameter", "-Wsign-conversion", "-Wfloat-conversion", + "-Wfloat-equal") JUCE_BEGIN_IGNORE_WARNINGS_MSVC(4100 4127 4244) // Sigh - X11.h eventually does a #define None 0L which doesn't work // with an enum in clap land being called None, so just undef it @@ -70,9 +71,8 @@ JUCE_END_IGNORE_WARNINGS_GCC_LIKE extern void *clapJuceExtensionCustomFactory(const char *); #endif -#if ! JUCE_MAC -template -using Point = juce::Point; +#if !JUCE_MAC +template using Point = juce::Point; #if JUCE_VERSION < 0x070006 using Component = juce::Component; #endif @@ -160,7 +160,7 @@ JUCE_BEGIN_IGNORE_WARNINGS_MSVC(4996) // allow strncpy // #undef CLAP_CHECKING_LEVEL // #define CLAP_CHECKING_LEVEL Maximal -/* Host context menus are only availble in JUCe 6.0.8 and later */ +/* Host context menus are only availble in JUCE 6.0.8 and later */ #if JUCE_VERSION >= 0x060008 class EditorContextMenu : public juce::HostProvidedContextMenu { @@ -238,8 +238,7 @@ class EditorContextMenu : public juce::HostProvidedContextMenu } else if (item_kind == CLAP_CONTEXT_MENU_ITEM_CHECK_ENTRY) { - const auto entry = - static_cast(item_data); + const auto entry = static_cast(item_data); juce::PopupMenu::Item item; item.itemID = ++menuIDCounter; @@ -278,8 +277,7 @@ class EditorContextMenu : public juce::HostProvidedContextMenu menuStack.pop_back(); // add the sub-menu to the menu one level up - menuStack.back().addSubMenu(currentSubMenuLabel, subMenu, - currentSubMenuEnabled); + menuStack.back().addSubMenu(currentSubMenuLabel, subMenu, currentSubMenuEnabled); } else if (item_kind == CLAP_CONTEXT_MENU_ITEM_TITLE) { @@ -293,7 +291,10 @@ class EditorContextMenu : public juce::HostProvidedContextMenu } // Currently, JUCE supports all the item kinds that CLAP supports! - bool supports(clap_context_menu_item_kind_t /*item_kind*/) const noexcept override { return true; } + bool supports(clap_context_menu_item_kind_t /*item_kind*/) const noexcept override + { + return true; + } }; MenuBuilder builder{host, &menuTarget}; }; @@ -467,8 +468,6 @@ class ClapJuceWrapper : public clap::helpers::Plugin< ~ClapJuceWrapper() override { - processor->editorBeingDeleted(editor.get()); - #if JUCE_LINUX if (_host.canUseTimerSupport()) { @@ -1684,104 +1683,184 @@ class ClapJuceWrapper : public clap::helpers::Plugin< } } - void componentMovedOrResized(Component &component, bool wasMoved, - bool wasResized) override + // START GUI CODE + bool implementsGui() const noexcept override { return processor->hasEditor(); } + bool guiIsApiSupported(const char *api, bool isFloating) noexcept override { - juce::ignoreUnused(wasMoved); - if (wasResized && _host.canUseGui()) - _host.guiRequestResize((uint32_t)component.getWidth(), (uint32_t)component.getHeight()); + if (isFloating) + return false; + + if (strcmp(api, CLAP_WINDOW_API_WIN32) == 0 || strcmp(api, CLAP_WINDOW_API_COCOA) == 0 || + strcmp(api, CLAP_WINDOW_API_X11) == 0) + return true; + + return false; } - std::unique_ptr editor; + struct EditorWrapperComponent : Component + { + using HostType = clap::helpers::HostProxy< + clap::helpers::MisbehaviourHandler::CLAP_MISBEHAVIOUR_HANDLER_LEVEL, + clap::helpers::CheckingLevel::CLAP_CHECKING_LEVEL>; + EditorWrapperComponent(HostType &_host, ClapJuceWrapper &_clapWrapper) + : host(_host), clapWrapper(_clapWrapper) + { + setOpaque(true); + setBroughtToFrontOnMouseClick(true); + } + + ~EditorWrapperComponent() override + { + if (editor != nullptr) + { + juce::PopupMenu::dismissAllActiveMenus(); + editor->processor.editorBeingDeleted(editor.get()); + } + } + + void createEditor(juce::AudioProcessor &plugin) + { + editor.reset(plugin.createEditorIfNeeded()); + + if (editor != nullptr) + { #if JUCE_VERSION >= 0x060008 - std::unique_ptr editorHostContext; + editorHostContext = + std::make_unique(host, clapWrapper.clapIDByParamPtr); + editor->setHostContext(editorHostContext.get()); +#endif +#if !JUCE_MAC + editor->setScaleFactor(clapWrapper.guiScaleFactor); #endif - bool implementsGui() const noexcept override { return processor->hasEditor(); } - bool guiCanResize() const noexcept override - { - if (editor) - return editor->isResizable(); - return true; - } - /* - * guiAdjustSize is called before guiSetSize and given the option to - * reset the size the host hands to the subsequent setSize. This is a - * relatively naive and unsatisfactory initial implementation. - */ - bool guiAdjustSize(uint32_t *w, uint32_t *h) noexcept override - { - if (!editor) - return false; + addAndMakeVisible(editor.get()); + editor->setTopLeftPosition(0, 0); - if (!editor->isResizable()) - return false; + lastBounds = getSizeToContainChild(); - auto cst = editor->getConstrainer(); + { + const juce::ScopedValueSetter resizingParentSetter{resizingParent, true}; + setBounds(lastBounds); + } + } + else + { + // if hasEditor() returns true then createEditorIfNeeded has to return a valid + // editor + jassertfalse; + } + } - if (!cst) - return true; // we have no constraints. Whaever is fine! + juce::Rectangle getSizeToContainChild() + { + if (editor != nullptr) + return getLocalArea(editor.get(), editor->getLocalBounds()); - auto minW = (uint32_t)cst->getMinimumWidth(); - auto maxW = (uint32_t)cst->getMaximumWidth(); - auto minH = (uint32_t)cst->getMinimumHeight(); - auto maxH = (uint32_t)cst->getMaximumHeight(); + return {}; + } - // There is no std::clamp in c++14 - auto width = juce::jlimit(minW, maxW, *w); - auto height = juce::jlimit(minH, maxH, *h); + juce::Rectangle convertToHostBounds(juce::Rectangle pluginRect) + { + const auto desktopScale = clapWrapper.guiScaleFactor; + if (juce::isWithin(desktopScale, 1.0f, 1.0e-3f)) + return pluginRect; - auto aspectRatio = (float)cst->getFixedAspectRatio(); + return {juce::roundToInt((float)pluginRect.getX() * desktopScale), + juce::roundToInt((float)pluginRect.getY() * desktopScale), + juce::roundToInt((float)pluginRect.getWidth() * desktopScale), + juce::roundToInt((float)pluginRect.getHeight() * desktopScale)}; + } - if (aspectRatio > 0.0f) + void resizeHostWindow() { - /* - * This is obviously an unsatisfactory algorithm, but we wanted to have - * something at least workable here. - * - * The problem with other algorithms I tried is that this function gets - * called by BWS for sub-single pixel motions on macOS, so it is hard to make - * a version which is *stable* (namely adjust(w,h); cw=w;ch=h; adjust(cw,ch); - * cw == w; ch == h) that deals with directions. I tried all sorts of stuff - * and then ran into vacation. - * - * So for now here's this approach. See the discussion in CJE PR #67 - * and interop-tracker issue #30. - */ - width = (uint32_t)std::round(aspectRatio * (float)height); + if (editor != nullptr) + { + auto editorBounds = getSizeToContainChild(); + auto b = + convertToHostBounds({0, 0, editorBounds.getWidth(), editorBounds.getHeight()}); + + { + const juce::ScopedValueSetter resizingParentSetter(resizingParent, true); + host.guiRequestResize((uint32_t)b.getWidth(), (uint32_t)b.getHeight()); + } + + setBounds(editorBounds.withPosition(0, 0)); + } } - *w = width; - *h = height; + void setEditorScaleFactor(float scale) + { + if (editor != nullptr) + { + auto prevEditorBounds = editor->getLocalArea(this, lastBounds); - return true; - } + { + const juce::ScopedValueSetter resizingChildSetter{resizingChild, true}; - bool guiSetSize(uint32_t width, uint32_t height) noexcept override - { - if (!editor) - return false; + editor->setScaleFactor(scale); + editor->setBounds(prevEditorBounds.withPosition(0, 0)); + } - if (!editor->isResizable()) - return false; + lastBounds = getSizeToContainChild(); - editor->setSize(static_cast(width), static_cast(height)); - return true; - } + resizeHostWindow(); + repaint(); + } + } - bool guiIsApiSupported(const char *api, bool isFloating) noexcept override - { - if (isFloating) - return false; + void paint(juce::Graphics &g) override { g.fillAll(juce::Colours::red); } + void resized() override + { + if (editor != nullptr) + { + if (!resizingParent) + { + auto newBounds = getLocalBounds(); - if (strcmp(api, CLAP_WINDOW_API_WIN32) == 0 || strcmp(api, CLAP_WINDOW_API_COCOA) == 0 || - strcmp(api, CLAP_WINDOW_API_X11) == 0) - return true; + { + const juce::ScopedValueSetter resizingChildSetter{resizingChild, + true}; + editor->setBounds(editor->getLocalArea(this, newBounds).withPosition(0, 0)); + } - return false; - } + lastBounds = newBounds; + } + } + } + + void childBoundsChanged(Component *) override + { + if (resizingChild) + return; + + auto newBounds = getSizeToContainChild(); + + if (newBounds != lastBounds) + { + resizeHostWindow(); + + repaint(); + + lastBounds = newBounds; + } + } + + HostType &host; + ClapJuceWrapper &clapWrapper; + std::unique_ptr editor; +#if JUCE_VERSION >= 0x060008 + std::unique_ptr editorHostContext; +#endif + + private: + juce::Rectangle lastBounds; + bool resizingChild = false, resizingParent = false; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EditorWrapperComponent) + }; + std::unique_ptr editorWrapper; bool guiParentAttached{false}; + float guiScaleFactor = 1.0f; bool guiCreate(const char *api, bool isFloating) noexcept override { juce::ignoreUnused(api); @@ -1791,36 +1870,18 @@ class ClapJuceWrapper : public clap::helpers::Plugin< return false; const juce::MessageManagerLock mmLock; - editor.reset(processor->createEditorIfNeeded()); - if (editor == nullptr) - return false; + if (editorWrapper == nullptr) + editorWrapper = std::make_unique(_host, *this); - if (editor != nullptr) - { -#if JUCE_VERSION >= 0x060008 - editorHostContext = std::make_unique(_host, clapIDByParamPtr); - editor->setHostContext(editorHostContext.get()); -#endif - editor->addComponentListener(this); - } - else - { - // if hasEditor() returns true then createEditorIfNeeded has to return a valid editor - jassertfalse; - } - - return editor != nullptr; + editorWrapper->createEditor(*processor); + return editorWrapper->editor != nullptr; } void guiDestroy() noexcept override { -#if JUCE_VERSION >= 0x060008 - editorHostContext.reset(); -#endif - processor->editorBeingDeleted(editor.get()); + editorWrapper.reset(nullptr); guiParentAttached = false; - editor.reset(nullptr); } bool guiSetParent(const clap_window *window) noexcept override @@ -1843,7 +1904,7 @@ class ClapJuceWrapper : public clap::helpers::Plugin< bool guiShow() noexcept override { #if JUCE_MAC || JUCE_LINUX || JUCE_WINDOWS - if (editor) + if (editorWrapper != nullptr && editorWrapper->editor != nullptr) { return guiParentAttached; } @@ -1851,12 +1912,108 @@ class ClapJuceWrapper : public clap::helpers::Plugin< return false; } + bool guiCanResize() const noexcept override + { + if (editorWrapper != nullptr && editorWrapper->editor != nullptr) + return editorWrapper->editor->isResizable(); + return true; + } + + bool guiSetScale(double scale) noexcept override + { + if (scale > 50) + { + // this is almost definitely a units error + scale *= 0.01; + } + guiScaleFactor = static_cast(scale); + + if (editorWrapper != nullptr) + { + editorWrapper->setEditorScaleFactor(guiScaleFactor); + return true; + } + + return true; + } + + /* + * guiAdjustSize is called before guiSetSize and given the option to + * reset the size the host hands to the subsequent setSize. This is a + * relatively naive and unsatisfactory initial implementation. + */ + bool guiAdjustSize(uint32_t *w, uint32_t *h) noexcept override + { + if (editorWrapper == nullptr || editorWrapper->editor == nullptr) + return false; + + if (!editorWrapper->editor->isResizable()) + return false; + + auto cst = editorWrapper->editor->getConstrainer(); + + if (!cst) + return true; // we have no constraints. Whatever is fine! + + const auto minBounds = + editorWrapper->convertToHostBounds({cst->getMinimumWidth(), cst->getMinimumHeight()}); + const auto maxBounds = + editorWrapper->convertToHostBounds({cst->getMaximumWidth(), cst->getMaximumHeight()}); + auto minW = (uint32_t)minBounds.getWidth(); + auto maxW = (uint32_t)maxBounds.getWidth(); + auto minH = (uint32_t)minBounds.getHeight(); + auto maxH = (uint32_t)maxBounds.getHeight(); + + // There is no std::clamp in c++14 + auto width = juce::jlimit(minW, maxW, *w); + auto height = juce::jlimit(minH, maxH, *h); + + auto aspectRatio = (float)cst->getFixedAspectRatio(); + + if (aspectRatio > 0.0f) + { + /* + * This is obviously an unsatisfactory algorithm, but we wanted to have + * something at least workable here. + * + * The problem with other algorithms I tried is that this function gets + * called by BWS for sub-single pixel motions on macOS, so it is hard to make + * a version which is *stable* (namely adjust(w,h); cw=w;ch=h; adjust(cw,ch); + * cw == w; ch == h) that deals with directions. I tried all sorts of stuff + * and then ran into vacation. + * + * So for now here's this approach. See the discussion in CJE PR #67 + * and interop-tracker issue #30. + */ + width = (uint32_t)std::round(aspectRatio * (float)height); + } + + *w = width; + *h = height; + + return true; + } + + bool guiSetSize(uint32_t width, uint32_t height) noexcept override + { + // std::cout << "GUI SET SIZE " << width << " " << height << std::endl; + if (editorWrapper == nullptr || editorWrapper->editor == nullptr) + return false; + + if (!editorWrapper->editor->isResizable()) + return false; + + const auto b = juce::Rectangle{(int)width, (int)height}; + editorWrapper->setSize(b.getWidth(), b.getHeight()); + return true; + } + bool guiGetSize(uint32_t *width, uint32_t *height) noexcept override { const juce::MessageManagerLock mmLock; - if (editor) + if (editorWrapper != nullptr && editorWrapper->editor != nullptr) { - auto b = editor->getBounds(); + const auto b = editorWrapper->getBounds(); *width = (uint32_t)b.getWidth(); *height = (uint32_t)b.getHeight(); return true; @@ -1868,6 +2025,7 @@ class ClapJuceWrapper : public clap::helpers::Plugin< } return false; } + // END GUI CODE protected: juce::CriticalSection stateInformationLock; @@ -1943,10 +2101,12 @@ class ClapJuceWrapper : public clap::helpers::Plugin< { #if JUCE_VERSION < 0x070006 juce::initialiseMacVST(); - auto hostWindow = juce::attachComponentToWindowRefVST(editor.get(), nsView, true); + auto hostWindow = juce::attachComponentToWindowRefVST(editorWrapper.get(), nsView, true); #else - const auto desktopFlags = juce::detail::PluginUtilities::getDesktopFlags (editor.get()); - auto hostWindow = juce::detail::VSTWindowUtilities::attachComponentToWindowRefVST(editor.get(), desktopFlags, nsView); + const auto desktopFlags = + juce::detail::PluginUtilities::getDesktopFlags(editorWrapper->editor.get()); + auto hostWindow = juce::detail::VSTWindowUtilities::attachComponentToWindowRefVST( + editorWrapper.get(), desktopFlags, nsView); #endif juce::ignoreUnused(hostWindow); return true; @@ -1958,24 +2118,23 @@ class ClapJuceWrapper : public clap::helpers::Plugin< { juce::ignoreUnused(displayName); const juce::MessageManagerLock mmLock; - editor->setVisible(false); - editor->addToDesktop(0, (void *)window); + editorWrapper->setVisible(false); + editorWrapper->addToDesktop(0, (void *)window); auto *display = juce::XWindowSystem::getInstance()->getDisplay(); - juce::X11Symbols::getInstance()->xReparentWindow(display, (Window)editor->getWindowHandle(), - window, 0, 0); - editor->setVisible(true); + juce::X11Symbols::getInstance()->xReparentWindow( + display, (Window)editorWrapper->getWindowHandle(), window, 0, 0); + editorWrapper->setVisible(true); return true; } #endif #if JUCE_WINDOWS - bool guiWin32Attach(clap_hwnd window) noexcept + bool guiWin32Attach(clap_hwnd window) const noexcept { - editor->setVisible(false); - editor->setOpaque(true); - editor->setTopLeftPosition(0, 0); - editor->addToDesktop(0, (void *)window); - editor->setVisible(true); + editorWrapper->setVisible(false); + editorWrapper->setTopLeftPosition(0, 0); + editorWrapper->addToDesktop(0, (void *)window); + editorWrapper->setVisible(true); return true; } #endif @@ -2098,7 +2257,7 @@ extern "C" #if JUCE_MINGW extern #endif - const CLAP_EXPORT struct clap_plugin_entry clap_entry = {CLAP_VERSION, ClapAdapter::clap_init, - ClapAdapter::clap_deinit, - ClapAdapter::clap_get_factory}; + const CLAP_EXPORT struct clap_plugin_entry clap_entry = { + CLAP_VERSION, ClapAdapter::clap_init, ClapAdapter::clap_deinit, + ClapAdapter::clap_get_factory}; }