From fcf19711226df62855211e953fec0b7dcdecce9e Mon Sep 17 00:00:00 2001 From: reuk Date: Tue, 23 Sep 2025 21:37:11 +0100 Subject: [PATCH] Plugin Client: Change scaling mechanism on Linux/Windows plugins --- .../detail/juce_PluginScaleFactorUtilities.h | 210 ++++++++++++++ .../juce_audio_plugin_client_LV2.cpp | 108 +++++--- .../juce_audio_plugin_client_VST2.cpp | 126 ++------- .../juce_audio_plugin_client_VST3.cpp | 257 +++++------------- 4 files changed, 361 insertions(+), 340 deletions(-) create mode 100644 modules/juce_audio_plugin_client/detail/juce_PluginScaleFactorUtilities.h diff --git a/modules/juce_audio_plugin_client/detail/juce_PluginScaleFactorUtilities.h b/modules/juce_audio_plugin_client/detail/juce_PluginScaleFactorUtilities.h new file mode 100644 index 0000000000..ebb53e1f57 --- /dev/null +++ b/modules/juce_audio_plugin_client/detail/juce_PluginScaleFactorUtilities.h @@ -0,0 +1,210 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +#pragma once + +namespace juce::detail +{ + +/** Keeps track of scale factors specified by the host and/or queried by the + the plugin. +*/ +class StoredScaleFactor +{ +public: + /** Sets a scale factor that originated from the host. + This scale will take precedence over other scale factors. + */ + StoredScaleFactor withHost (float x) const { return withMember (*this, &StoredScaleFactor::host, x); } + + /** Sets a scale factor that originated from the plugin. + This scale will only be used if there's no host-provided scale. + Defaults to 1.0f. + */ + StoredScaleFactor withInternal (float x) const { return withMember (*this, &StoredScaleFactor::internal, x); } + + /** Returns the host-provided scale, if any, or the internal scale otherwise. */ + float get() const { return host.value_or (internal); } + + /** Returns true if this object holds a host-originated scale, or false otherwise. */ + bool isHostScale() const { return host.has_value(); } + +private: + std::optional host; + float internal = 1.0f; +}; + +class PluginScaleFactorManagerListener +{ +public: + virtual ~PluginScaleFactorManagerListener() = default; + virtual void peerBoundsDidUpdate() = 0; +}; + +class PluginScaleFactorManager : private Timer, + private ComponentListener +{ +public: + ~PluginScaleFactorManager() override + { + stopTimer(); + } + + void startObserving (Component& comp) + { + observed = ∁ + observed->addComponentListener (this); + applyScaleFactor (scale); + + #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE + if (! scale.isHostScale()) + startTimer (500); + #endif + } + + void stopObserving ([[maybe_unused]] Component& comp) + { + stopTimer(); + jassert (&comp == observed.getComponent()); + observed->removeComponentListener (this); + observed = nullptr; + } + + void addListener (PluginScaleFactorManagerListener& l) + { + listeners.add (&l); + } + + void removeListener (PluginScaleFactorManagerListener& l) + { + listeners.remove (&l); + } + + void setHostScale (float x) + { + stopTimer(); + applyScaleFactor (scale.withHost (x)); + } + + std::optional getHostScale() const + { + return scale.isHostScale() ? std::optional (scale.get()) : std::nullopt; + } + + Rectangle convertToHostBounds (Rectangle pluginRect) const + { + jassert (observed != nullptr); + return (observed->localAreaToGlobal (pluginRect) * getPlatformAndDesktopScale()).withZeroOrigin().toNearestIntEdges(); + } + + Rectangle convertFromHostBounds (Rectangle hostViewRect) const + { + jassert (observed != nullptr); + return observed->getLocalArea (nullptr, hostViewRect.toFloat() / getPlatformAndDesktopScale()).withZeroOrigin(); + } + + #if JUCE_WINDOWS + static double getScaleFactorForWindow (HWND h) + { + return (double) GetDpiForWindow (h) / USER_DEFAULT_SCREEN_DPI; + } + #endif + +private: + void componentParentHierarchyChanged (Component&) override + { + if (auto* peer = observed->getPeer()) + peer->setCustomPlatformScaleFactor (getHostScale()); + } + + float getScaleFactorForWindow() const + { + if (auto* comp = observed.getComponent()) + if (auto* peer = comp->getPeer()) + return (float) peer->getPlatformScaleFactor(); + + return 1.0f; + } + + void timerCallback() override + { + if (const auto estimatedScale = getScaleFactorForWindow(); estimatedScale > 0.0f) + applyScaleFactor (scale.withInternal (estimatedScale)); + } + + void applyScaleFactor (StoredScaleFactor newFactor) + { + const auto previous = std::exchange (scale, newFactor).get(); + const auto current = scale.get(); + const auto scalesEqual = approximatelyEqual (current, previous); + + if (observed == nullptr) + return; + + if (scale.isHostScale()) + if (auto* peer = observed->getPeer()) + peer->setCustomPlatformScaleFactor (current); + + if (scalesEqual) + return; + + #if JUCE_LINUX || JUCE_BSD + const MessageManagerLock mmLock; + #endif + + if (auto* peer = observed->getPeer()) + { + peer->updateBounds(); + listeners.call ([] (auto& l) { l.peerBoundsDidUpdate(); }); + } + } + + float getPlatformAndDesktopScale() const + { + jassert (observed != nullptr); + return (observed->getDesktopScaleFactor() * std::invoke ([&] + { + if (auto* peer = observed->getPeer()) + return (float) peer->getPlatformScaleFactor(); + + return scale.get(); + })); + } + + ListenerList listeners; + Component::SafePointer observed; + StoredScaleFactor scale; +}; + +} // namespace juce::detail diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_LV2.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_LV2.cpp index 8cb4d331a9..f11e6fc151 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_LV2.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_LV2.cpp @@ -49,6 +49,7 @@ #include #include #include +#include #include #include @@ -769,6 +770,8 @@ public: return result; } + detail::PluginScaleFactorManager& getScaleManager() { return scaleManager; } + private: void audioProcessorParameterChanged (AudioProcessor*, int, float) override {} @@ -800,6 +803,8 @@ private: ScopedJuceInitialiser_GUI scopedJuceInitialiser; + detail::PluginScaleFactorManager scaleManager; + #if JUCE_LINUX || JUCE_BSD SharedResourcePointer messageThread; #endif @@ -1509,7 +1514,7 @@ LV2_SYMBOL_EXPORT const LV2_Descriptor* lv2_descriptor (uint32_t index) return &descriptor; } -static Optional findScaleFactor (const LV2_URID_Map* symap, const LV2_Options_Option* options) +static std::optional findScaleFactor (const LV2_URID_Map* symap, const LV2_Options_Option* options) { if (options == nullptr || symap == nullptr) return {}; @@ -1521,7 +1526,7 @@ static Optional findScaleFactor (const LV2_URID_Map* symap, const LV2_Opt } class LV2UIInstance final : private Component, - private ComponentListener + private detail::PluginScaleFactorManagerListener { public: LV2UIInstance (const char*, @@ -1533,14 +1538,13 @@ public: LV2UI_Widget parentIn, const LV2_URID_Map* symapIn, const LV2UI_Resize* resizeFeatureIn, - Optional scaleFactorIn) + std::optional scaleFactorIn) : writeFunction (writeFunctionIn), controller (controllerIn), plugin (pluginIn), parent (parentIn), symap (symapIn), resizeFeature (resizeFeatureIn), - scaleFactor (scaleFactorIn), editor (plugin->createEditor()) { jassert (plugin != nullptr); @@ -1550,6 +1554,9 @@ public: if (editor == nullptr) return; + plugin->getScaleManager().addListener (*this); + plugin->getScaleManager().startObserving (*this); + const auto bounds = getSizeToContainChild(); setSize (bounds.getWidth(), bounds.getHeight()); @@ -1560,18 +1567,22 @@ public: setVisible (false); removeFromDesktop(); addToDesktop (detail::PluginUtilities::getDesktopFlags (editor.get()), parent); - editor->addComponentListener (this); *widget = getWindowHandle(); setVisible (true); - editor->setScaleFactor (getScaleFactor()); + if (scaleFactorIn.has_value()) + plugin->getScaleManager().setHostScale (*scaleFactorIn); + requestResize(); } ~LV2UIInstance() override { + plugin->getScaleManager().stopObserving (*this); + plugin->getScaleManager().removeListener (*this); + plugin->editorBeingDeleted (editor.get()); } @@ -1582,8 +1593,17 @@ public: // Called when the host requests a resize int resize (int width, int height) { - const ScopedValueSetter scope (hostRequestedResize, true); - setSize (width, height); + const ScopedValueSetter scope (resizingChild, true); + + if (editor == nullptr) + return 0; + + const auto logicalBounds = plugin->getScaleManager().convertFromHostBounds ({ width, height }).toNearestIntEdges(); + editor->setBoundsConstrained (logicalBounds.withZeroOrigin()); + + const auto bounds = getSizeToContainChild(); + setSize (bounds.getWidth(), bounds.getHeight()); + return 0; } @@ -1595,34 +1615,34 @@ public: #endif } - void resized() override - { - const ScopedValueSetter scope (hostRequestedResize, true); - - if (editor != nullptr) - { - const auto localArea = editor->getLocalArea (this, getLocalBounds()); - editor->setBoundsConstrained ({ localArea.getWidth(), localArea.getHeight() }); - } - } - void paint (Graphics& g) override { g.fillAll (Colours::black); } + void parentSizeChanged() override + { + if (editor == nullptr) + return; + + requestResize(); + editor->repaint(); + } + uint32_t getOptions (LV2_Options_Option* options) { const auto scaleFactorUrid = symap->map (symap->handle, LV2_UI__scaleFactor); - const auto floatUrid = symap->map (symap->handle, LV2_ATOM__Float);; + const auto floatUrid = symap->map (symap->handle, LV2_ATOM__Float); for (auto* opt = options; opt->key != 0; ++opt) { if (opt->context != LV2_OPTIONS_INSTANCE || opt->subject != 0 || opt->key != scaleFactorUrid) continue; - if (scaleFactor.hasValue()) + if (const auto optionalHostScale = plugin->getScaleManager().getHostScale()) { + hostScale = *optionalHostScale; + opt->type = floatUrid; opt->size = sizeof (float); - opt->value = &(*scaleFactor); + opt->value = &hostScale; } } @@ -1632,7 +1652,7 @@ public: uint32_t setOptions (const LV2_Options_Option* options) { const auto scaleFactorUrid = symap->map (symap->handle, LV2_UI__scaleFactor); - const auto floatUrid = symap->map (symap->handle, LV2_ATOM__Float);; + const auto floatUrid = symap->map (symap->handle, LV2_ATOM__Float); for (auto* opt = options; opt->key != 0; ++opt) { @@ -1645,20 +1665,13 @@ public: continue; } - scaleFactor = *static_cast (opt->value); - updateScale(); + plugin->getScaleManager().setHostScale (*static_cast (opt->value)); } return LV2_OPTIONS_SUCCESS; } private: - void updateScale() - { - editor->setScaleFactor (getScaleFactor()); - requestResize(); - } - Rectangle getSizeToContainChild() const { if (editor != nullptr) @@ -1667,15 +1680,17 @@ private: return {}; } - float getScaleFactor() const noexcept + void peerBoundsDidUpdate() override { - return scaleFactor.hasValue() ? *scaleFactor : 1.0f; + requestResize(); } - void componentMovedOrResized (Component&, bool, bool wasResized) override + void childBoundsChanged (Component*) override { - if (! hostRequestedResize && wasResized) - requestResize(); + if (resizingChild) + return; + + requestResize(); } void write (uint32_t portIndex, uint32_t bufferSize, uint32_t portProtocol, const void* data) @@ -1688,16 +1703,19 @@ private: if (editor == nullptr) return; - const auto bounds = getSizeToContainChild(); - if (resizeFeature == nullptr) return; - if (auto* fn = resizeFeature->ui_resize) - fn (resizeFeature->handle, bounds.getWidth(), bounds.getHeight()); + const auto logicalBounds = getSizeToContainChild(); + const auto physicalBounds = plugin->getScaleManager().convertToHostBounds (logicalBounds.toFloat()); - setSize (bounds.getWidth(), bounds.getHeight()); - repaint(); + if (auto* fn = resizeFeature->ui_resize) + fn (resizeFeature->handle, physicalBounds.getWidth(), physicalBounds.getHeight()); + + setBounds (logicalBounds.withZeroOrigin()); + + if (auto* peer = getPeer()) + peer->updateBounds(); } #if JUCE_LINUX || JUCE_BSD @@ -1710,9 +1728,9 @@ private: LV2UI_Widget parent; const LV2_URID_Map* symap = nullptr; const LV2UI_Resize* resizeFeature = nullptr; - Optional scaleFactor; std::unique_ptr editor; - bool hostRequestedResize = false; + float hostScale = 0.0f; + bool resizingChild = false; JUCE_LEAK_DETECTOR (LV2UIInstance) }; @@ -1757,7 +1775,7 @@ LV2_SYMBOL_EXPORT const LV2UI_Descriptor* lv2ui_descriptor (uint32_t index) auto* resizeFeature = findMatchingFeatureData (features, LV2_UI__resize); - const auto* symap = findMatchingFeatureData (features, LV2_URID__map); + const auto* symap = findMatchingFeatureData (features, LV2_URID__map); const auto scaleFactor = findScaleFactor (symap, findMatchingFeatureData (features, LV2_OPTIONS__options)); return new LV2UIInstance { pluginUri, diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp index 6a92f47356..4f1aa33a62 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST2.cpp @@ -111,6 +111,7 @@ JUCE_END_IGNORE_WARNINGS_GCC_LIKE #define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1 #include +#include using namespace juce; @@ -131,13 +132,6 @@ using namespace juce; class JuceVSTWrapper; static bool recursionCheck = false; -namespace juce -{ - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - JUCE_API double getScaleFactorForWindow (HWND); - #endif -} - //============================================================================== #if JUCE_WINDOWS @@ -348,7 +342,7 @@ public: #if JucePlugin_IsSynth vstEffect.flags |= Vst2::effFlagsIsSynth; #else - if (processor->getTailLengthSeconds() == 0.0) + if (approximatelyEqual (processor->getTailLengthSeconds(), 0.0)) vstEffect.flags |= Vst2::effFlagsNoSoundInStop; #endif @@ -823,7 +817,7 @@ public: if (auto* ed = processor->createEditorIfNeeded()) { setHasEditorFlag (true); - editorComp.reset (new EditorCompWrapper (*this, *ed, editorScaleFactor)); + editorComp.reset (new EditorCompWrapper (*this, *ed)); } else { @@ -928,18 +922,13 @@ public: //============================================================================== // A component to hold the AudioProcessorEditor, and cope with some housekeeping // chores when it changes or repaints. - struct EditorCompWrapper final : public Component - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - , public Timer - #endif + struct EditorCompWrapper final : public Component, + private detail::PluginScaleFactorManagerListener { - EditorCompWrapper (JuceVSTWrapper& w, AudioProcessorEditor& editor, [[maybe_unused]] float initialScale) + EditorCompWrapper (JuceVSTWrapper& w, AudioProcessorEditor& editor) : wrapper (w) { editor.setOpaque (true); - #if ! JUCE_MAC - editor.setScaleFactor (initialScale); - #endif addAndMakeVisible (editor); auto editorBounds = getSizeToContainChild(); @@ -951,10 +940,15 @@ public: #endif setOpaque (true); + + wrapper.scaleManager.addListener (*this); + wrapper.scaleManager.startObserving (*this); } ~EditorCompWrapper() override { + wrapper.scaleManager.stopObserving (*this); + wrapper.scaleManager.removeListener (*this); deleteAllChildren(); // note that we can't use a std::unique_ptr because the editor may // have been transferred to another parent which takes over ownership. } @@ -966,8 +960,8 @@ public: void getEditorBounds (Vst2::ERect& bounds) { - auto editorBounds = getSizeToContainChild(); - bounds = convertToHostBounds ({ 0, 0, (int16) editorBounds.getHeight(), (int16) editorBounds.getWidth() }); + auto editorBounds = getSizeToContainChild().toFloat().withZeroOrigin(); + bounds = createViewRect (wrapper.scaleManager.convertToHostBounds (editorBounds)); } void attachToHost (VstOpCodeArguments args) @@ -989,9 +983,6 @@ public: // and we need to ensure that the X server knows that our window has been attached // before that happens. X11Symbols::getInstance()->xFlush (display); - #elif JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - checkHostWindowScaleFactor (true); - startTimer (500); #endif #elif JUCE_MAC hostWindow = detail::VSTWindowUtilities::attachComponentToWindowRefVST (this, desktopFlags, args.ptr); @@ -1015,26 +1006,6 @@ public: return dynamic_cast (getChildComponent (0)); } - void resized() override - { - if (auto* pluginEditor = getEditorComp()) - { - if (! resizingParent) - { - auto newBounds = getLocalBounds(); - - { - const ScopedValueSetter resizingChildSetter (resizingChild, true); - pluginEditor->setBounds (pluginEditor->getLocalArea (this, newBounds).withPosition (0, 0)); - } - - lastBounds = newBounds; - } - - updateWindowSize(); - } - } - void parentSizeChanged() override { updateWindowSize(); @@ -1063,9 +1034,14 @@ public: return {}; } + static Vst2::ERect createViewRect (juce::Rectangle r) + { + return { (int16) r.getY(), (int16) r.getX(), (int16) r.getBottom(), (int16) r.getRight() }; + } + void resizeHostWindow (juce::Rectangle bounds) { - auto rect = convertToHostBounds ({ 0, 0, (int16) bounds.getHeight(), (int16) bounds.getWidth() }); + auto rect = createViewRect (wrapper.scaleManager.convertToHostBounds (bounds.toFloat())); const auto newWidth = rect.right - rect.left; const auto newHeight = rect.bottom - rect.top; @@ -1147,24 +1123,6 @@ public: #endif } - void setContentScaleFactor (float scale) - { - if (auto* pluginEditor = getEditorComp()) - { - auto prevEditorBounds = pluginEditor->getLocalArea (this, lastBounds); - - { - const ScopedValueSetter resizingChildSetter (resizingChild, true); - - pluginEditor->setScaleFactor (scale); - pluginEditor->setBounds (prevEditorBounds.withPosition (0, 0)); - } - - lastBounds = getSizeToContainChild(); - updateWindowSize(); - } - } - #if JUCE_WINDOWS void mouseDown (const MouseEvent&) override { @@ -1179,24 +1137,14 @@ public: if (HWND parent = findMDIParentOf ((HWND) getWindowHandle())) SetWindowPos (parent, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE); } - - #if JUCE_WIN_PER_MONITOR_DPI_AWARE - void checkHostWindowScaleFactor (bool force = false) - { - auto hostWindowScale = (float) getScaleFactorForWindow ((HostWindowType) hostWindow); - - if (force || (hostWindowScale > 0.0f && ! approximatelyEqual (hostWindowScale, wrapper.editorScaleFactor))) - wrapper.handleSetContentScaleFactor (hostWindowScale, force); - } - - void timerCallback() override - { - checkHostWindowScaleFactor(); - } - #endif #endif private: + void peerBoundsDidUpdate() override + { + updateWindowSize(); + } + void updateWindowSize() { if (! resizingParent @@ -1225,20 +1173,6 @@ public: } } - //============================================================================== - static Vst2::ERect convertToHostBounds (const Vst2::ERect& rect) - { - auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); - - if (approximatelyEqual (desktopScale, 1.0f)) - return rect; - - return { (int16) roundToInt (rect.top * desktopScale), - (int16) roundToInt (rect.left * desktopScale), - (int16) roundToInt (rect.bottom * desktopScale), - (int16) roundToInt (rect.right * desktopScale) }; - } - //============================================================================== #if JUCE_LINUX || JUCE_BSD SharedResourcePointer hostEventLoop; @@ -1988,15 +1922,7 @@ private: const MessageManagerLock mmLock; #endif - #if ! JUCE_MAC - if (force || ! approximatelyEqual (scale, editorScaleFactor)) - { - editorScaleFactor = scale; - - if (editorComp != nullptr) - editorComp->setContentScaleFactor (editorScaleFactor); - } - #endif + scaleManager.setHostScale (scale); return 1; } @@ -2112,7 +2038,7 @@ private: CriticalSection stateInformationLock; juce::MemoryBlock chunkMemory; uint32 chunkMemoryTime = 0; - float editorScaleFactor = 1.0f; + detail::PluginScaleFactorManager scaleManager; std::unique_ptr editorComp; Vst2::ERect editorRect; MidiBuffer midiEvents; diff --git a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp index c7b02c3774..78d6bed9a5 100644 --- a/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp +++ b/modules/juce_audio_plugin_client/juce_audio_plugin_client_VST3.cpp @@ -57,6 +57,7 @@ JUCE_BEGIN_NO_SANITIZE ("vptr") #include #include #include +#include #include #include #include @@ -120,11 +121,6 @@ namespace juce using namespace Steinberg; -//============================================================================== -#if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - double getScaleFactorForWindow (HWND); -#endif - //============================================================================== #if JUCE_LINUX || JUCE_BSD @@ -1672,9 +1668,7 @@ private: int lastLatencySamples = 0; bool blueCatPatchwork = isBlueCatHost (hostContext.get()); - #if ! JUCE_MAC - float lastScaleFactorReceived = 1.0f; - #endif + detail::PluginScaleFactorManager scaleManager; InterfaceResultWithDeferredAddRef queryInterfaceInternal (const TUID targetIID) { @@ -1995,27 +1989,9 @@ private: const auto desktopFlags = detail::PluginUtilities::getDesktopFlags (component->pluginEditor.get()); #if JUCE_WINDOWS || JUCE_LINUX || JUCE_BSD - // If the plugin was last opened at a particular scale, try to reapply that scale here. - // Note that we do this during attach(), rather than in JuceVST3Editor(). During the - // constructor, we don't have a host plugFrame, so - // ContentWrapperComponent::resizeHostWindow() won't do anything, and the content - // wrapper component will be left at the wrong size. - applyScaleFactor (StoredScaleFactor{}.withInternal (owner->lastScaleFactorReceived)); - - // Check the host scale factor *before* calling addToDesktop, so that the initial - // window size during addToDesktop is correct for the current platform scale factor. - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - component->checkHostWindowScaleFactor(); - #endif - component->setOpaque (true); component->addToDesktop (desktopFlags, systemWindow); component->setVisible (true); - - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - component->startTimer (500); - #endif - #else macHostWindow = detail::VSTWindowUtilities::attachComponentToWindowRefVST (component.get(), desktopFlags, parent); #endif @@ -2062,12 +2038,12 @@ private: } lastReportedSize.reset(); - rect = roundToViewRect (convertFromHostBounds (*newSize)); if (component == nullptr) return kResultTrue; - component->setSize (rect.getWidth(), rect.getHeight()); + const auto rounded = owner->scaleManager.convertFromHostBounds (createRectangle (*newSize)).toNearestIntEdges(); + component->onSize (rounded.getWidth(), rounded.getHeight()); #if JUCE_MAC if (cubase10Workaround != nullptr) @@ -2078,7 +2054,11 @@ private: #endif { if (auto* peer = component->getPeer()) - peer->updateBounds(); + { + peer->setBounds ((Rectangle { newSize->getWidth(), newSize->getHeight() }.toFloat() + / peer->getPlatformScaleFactor()).toNearestInt(), + false); + } } return kResultTrue; @@ -2091,15 +2071,12 @@ private: return kResultFalse; #endif - if (size == nullptr || component == nullptr) + if (size == nullptr || component == nullptr || component->pluginEditor == nullptr) return kResultFalse; const auto editorBounds = component->getSizeToContainChild(); - const auto sizeToReport = lastReportedSize.has_value() - ? *lastReportedSize - : convertToHostBounds (editorBounds.withZeroOrigin().toFloat()); - lastReportedSize = *size = sizeToReport; + lastReportedSize = *size = lastReportedSize.value_or (createViewRect (owner->scaleManager.convertToHostBounds (editorBounds.withZeroOrigin().toFloat()))); return kResultTrue; } @@ -2127,14 +2104,14 @@ private: auto constrainedRect = component->getLocalArea (editor, editor->getLocalBounds()) .getSmallestIntegerContainer(); - *rectToCheck = roundToViewRect (convertFromHostBounds (*rectToCheck)); + *rectToCheck = createViewRect (owner->scaleManager.convertFromHostBounds (createRectangle (*rectToCheck)).toNearestIntEdges()); rectToCheck->right = rectToCheck->left + roundToInt (constrainedRect.getWidth()); rectToCheck->bottom = rectToCheck->top + roundToInt (constrainedRect.getHeight()); - *rectToCheck = convertToHostBounds (createRectangle (*rectToCheck)); + *rectToCheck = createViewRect (owner->scaleManager.convertToHostBounds (createRectangle (*rectToCheck).toFloat())); } else if (auto* constrainer = editor->getConstrainer()) { - const auto clientBounds = convertFromHostBounds (*rectToCheck); + const auto clientBounds = owner->scaleManager.convertFromHostBounds (createRectangle (*rectToCheck)); const auto editorBounds = editor->getLocalArea (component.get(), clientBounds); auto minW = (float) constrainer->getMinimumWidth(); @@ -2185,8 +2162,8 @@ private: auto constrainedRect = component->getLocalArea (editor, Rectangle (width, height)); - *rectToCheck = convertToHostBounds (clientBounds.withWidth (constrainedRect.getWidth()) - .withHeight (constrainedRect.getHeight())); + *rectToCheck = createViewRect (owner->scaleManager.convertToHostBounds (clientBounds.withWidth (constrainedRect.getWidth()) + .withHeight (constrainedRect.getHeight()))); } } @@ -2200,14 +2177,14 @@ private: tresult PLUGIN_API setContentScaleFactor ([[maybe_unused]] const IPlugViewContentScaleSupport::ScaleFactor factor) override { #if ! JUCE_MAC - const auto scaleToApply = [&] + const auto scaleToApply = std::invoke ([&] { #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE // Cubase 10 only sends integer scale factors, so correct this for fractional scales if (detail::PluginUtilities::getHostType().type != PluginHostType::SteinbergCubase10) return factor; - const auto hostWindowScale = (IPlugViewContentScaleSupport::ScaleFactor) getScaleFactorForWindow (static_cast (systemWindow)); + const auto hostWindowScale = (decltype (factor)) detail::PluginScaleFactorManager::getScaleFactorForWindow (static_cast (systemWindow)); if (hostWindowScale <= 0.0 || approximatelyEqual (factor, hostWindowScale)) return factor; @@ -2216,9 +2193,9 @@ private: #else return factor; #endif - }(); + }); - applyScaleFactor (scaleFactor.withHost (scaleToApply)); + owner->scaleManager.setHostScale (scaleToApply); return kResultTrue; #else @@ -2277,53 +2254,36 @@ private: onSize (&viewRect); } - static ViewRect roundToViewRect (Rectangle r) + static ViewRect createViewRect (Rectangle r) { - const auto rounded = r.toNearestIntEdges(); - return { rounded.getX(), - rounded.getY(), - rounded.getRight(), - rounded.getBottom() }; + return { r.getX(), r.getY(), r.getRight(), r.getBottom() }; } - static Rectangle createRectangle (ViewRect viewRect) + static Rectangle createRectangle (ViewRect viewRect) { - return Rectangle::leftTopRightBottom ((float) viewRect.left, - (float) viewRect.top, - (float) viewRect.right, - (float) viewRect.bottom); - } - - static ViewRect convertToHostBounds (Rectangle pluginRect) - { - const auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); - return roundToViewRect (approximatelyEqual (desktopScale, 1.0f) ? pluginRect - : pluginRect * desktopScale); - } - - static Rectangle convertFromHostBounds (ViewRect hostViewRect) - { - const auto desktopScale = Desktop::getInstance().getGlobalScaleFactor(); - const auto hostRect = createRectangle (hostViewRect); - - return approximatelyEqual (desktopScale, 1.0f) ? hostRect - : (hostRect / desktopScale); + return Rectangle::leftTopRightBottom ((int) viewRect.left, + (int) viewRect.top, + (int) viewRect.right, + (int) viewRect.bottom); } //============================================================================== - struct ContentWrapperComponent final : public Component - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - , public Timer - #endif + struct ContentWrapperComponent final : public Component, + private detail::PluginScaleFactorManagerListener { ContentWrapperComponent (JuceVST3Editor& editor) : owner (editor) { setOpaque (true); setBroughtToFrontOnMouseClick (true); + owner.owner->scaleManager.addListener (*this); + owner.owner->scaleManager.startObserving (*this); } ~ContentWrapperComponent() override { + owner.owner->scaleManager.stopObserving (*this); + owner.owner->scaleManager.removeListener (*this); + if (pluginEditor != nullptr) { PopupMenu::dismissAllActiveMenus(); @@ -2349,19 +2309,11 @@ private: &owner); pluginEditor->setHostContext (editorHostContext.get()); - #if ! JUCE_MAC - pluginEditor->setScaleFactor (owner.scaleFactor.get()); - #endif addAndMakeVisible (pluginEditor.get()); pluginEditor->setTopLeftPosition (0, 0); - lastBounds = getSizeToContainChild(); - - { - const ScopedValueSetter resizingParentSetter (resizingParent, true); - setBounds (lastBounds); - } + setBounds (getSizeToContainChild()); resizeHostWindow(); } @@ -2377,7 +2329,7 @@ private: g.fillAll (Colours::black); } - juce::Rectangle getSizeToContainChild() + Rectangle getSizeToContainChild() { if (pluginEditor != nullptr) return getLocalArea (pluginEditor.get(), pluginEditor->getLocalBounds()); @@ -2385,42 +2337,28 @@ private: return {}; } + void onSize (int w, int h) + { + const ScopedValueSetter resizingChildSetter (resizingChild, true); + + if (pluginEditor != nullptr) + { + const auto editorArea = pluginEditor->getLocalArea (this, Rectangle { w, h }); + pluginEditor->setBoundsConstrained (editorArea.withZeroOrigin()); + } + } + void childBoundsChanged (Component*) override { if (resizingChild) return; - auto newBounds = getSizeToContainChild(); + resizeHostWindow(); - if (newBounds != lastBounds) - { - resizeHostWindow(); - - #if JUCE_LINUX || JUCE_BSD - if (detail::PluginUtilities::getHostType().isBitwigStudio()) - repaint(); - #endif - - lastBounds = newBounds; - } - } - - void resized() override - { - if (pluginEditor != nullptr) - { - if (! resizingParent) - { - auto newBounds = getLocalBounds(); - - { - const ScopedValueSetter resizingChildSetter (resizingChild, true); - pluginEditor->setBounds (pluginEditor->getLocalArea (this, newBounds).withZeroOrigin()); - } - - lastBounds = newBounds; - } - } + #if JUCE_LINUX || JUCE_BSD + if (detail::PluginUtilities::getHostType().isBitwigStudio()) + repaint(); + #endif } void parentSizeChanged() override @@ -2439,13 +2377,9 @@ private: if (owner.plugFrame != nullptr) { auto editorBounds = getSizeToContainChild(); - auto newSize = convertToHostBounds (editorBounds.withZeroOrigin().toFloat()); - - { - const ScopedValueSetter resizingParentSetter (resizingParent, true); - owner.plugFrame->resizeView (&owner, &newSize); - } + auto newSize = owner.createViewRect (owner.owner->scaleManager.convertToHostBounds (editorBounds.withZeroOrigin().toFloat())); + owner.plugFrame->resizeView (&owner, &newSize); auto host = detail::PluginUtilities::getHostType(); #if JUCE_MAC @@ -2458,48 +2392,17 @@ private: } } - void setEditorScaleFactor (float scale) - { - if (pluginEditor != nullptr) - { - auto prevEditorBounds = pluginEditor->getLocalArea (this, lastBounds); - - { - const ScopedValueSetter resizingChildSetter (resizingChild, true); - - pluginEditor->setScaleFactor (scale); - pluginEditor->setBounds (prevEditorBounds.withZeroOrigin()); - } - - lastBounds = getSizeToContainChild(); - - resizeHostWindow(); - repaint(); - } - } - - #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE - void checkHostWindowScaleFactor() - { - const auto estimatedScale = (float) getScaleFactorForWindow (static_cast (owner.systemWindow)); - - if (estimatedScale > 0.0) - owner.applyScaleFactor (owner.scaleFactor.withInternal (estimatedScale)); - } - - void timerCallback() override - { - checkHostWindowScaleFactor(); - } - #endif - std::unique_ptr pluginEditor; private: + void peerBoundsDidUpdate() override + { + resizeHostWindow(); + } + JuceVST3Editor& owner; std::unique_ptr editorHostContext; - Rectangle lastBounds; - bool resizingChild = false, resizingParent = false; + bool resizingChild = false; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ContentWrapperComponent) }; @@ -2562,44 +2465,8 @@ private: }; std::unique_ptr cubase10Workaround; - #else - class StoredScaleFactor - { - public: - StoredScaleFactor withHost (float x) const { return withMember (*this, &StoredScaleFactor::host, x); } - StoredScaleFactor withInternal (float x) const { return withMember (*this, &StoredScaleFactor::internal, x); } - float get() const { return host.value_or (internal); } - - private: - std::optional host; - float internal = 1.0f; - }; - - void applyScaleFactor (const StoredScaleFactor newFactor) - { - const auto previous = std::exchange (scaleFactor, newFactor).get(); - - if (approximatelyEqual (previous, scaleFactor.get())) - return; - - if (owner != nullptr) - owner->lastScaleFactorReceived = scaleFactor.get(); - - if (component != nullptr) - { - #if JUCE_LINUX || JUCE_BSD - const MessageManagerLock mmLock; - #endif - component->setEditorScaleFactor (scaleFactor.get()); - } - } - - StoredScaleFactor scaleFactor; - - #if JUCE_WINDOWS - detail::WindowsHooks hooks; - #endif - + #elif JUCE_WINDOWS + detail::WindowsHooks hooks; #endif //==============================================================================