| @@ -97,7 +97,7 @@ using namespace Steinberg; | |||||
| #endif | #endif | ||||
| #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | #if JUCE_WINDOWS && JUCE_WIN_PER_MONITOR_DPI_AWARE | ||||
| extern JUCE_API double getScaleFactorForWindow (HWND); | |||||
| double getScaleFactorForWindow (HWND); | |||||
| #endif | #endif | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -1333,7 +1333,6 @@ private: | |||||
| //============================================================================== | //============================================================================== | ||||
| struct VST3PluginWindow : public AudioProcessorEditor, | struct VST3PluginWindow : public AudioProcessorEditor, | ||||
| private ComponentMovementWatcher, | private ComponentMovementWatcher, | ||||
| private ComponentPeer::ScaleFactorListener, | |||||
| private IPlugFrame | private IPlugFrame | ||||
| { | { | ||||
| VST3PluginWindow (AudioPluginInstance* owner, IPlugView* pluginView) | VST3PluginWindow (AudioPluginInstance* owner, IPlugView* pluginView) | ||||
| @@ -1360,8 +1359,6 @@ struct VST3PluginWindow : public AudioProcessorEditor, | |||||
| if (scaleInterface != nullptr) | if (scaleInterface != nullptr) | ||||
| scaleInterface->release(); | scaleInterface->release(); | ||||
| removeScaleFactorListener(); | |||||
| #if JUCE_LINUX || JUCE_BSD | #if JUCE_LINUX || JUCE_BSD | ||||
| embeddedComponent.removeClient(); | embeddedComponent.removeClient(); | ||||
| #endif | #endif | ||||
| @@ -1421,16 +1418,19 @@ struct VST3PluginWindow : public AudioProcessorEditor, | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| void componentPeerChanged() override | |||||
| void componentPeerChanged() override {} | |||||
| /* Convert from the component's coordinate system to the hosted VST3's coordinate system. */ | |||||
| ViewRect componentToVST3Rect (Rectangle<int> r) const | |||||
| { | { | ||||
| removeScaleFactorListener(); | |||||
| currentPeer = getTopLevelComponent()->getPeer(); | |||||
| const auto physical = localAreaToGlobal (r) * nativeScaleFactor * getDesktopScaleFactor(); | |||||
| return { 0, 0, physical.getWidth(), physical.getHeight() }; | |||||
| } | |||||
| if (currentPeer != nullptr) | |||||
| { | |||||
| currentPeer->addScaleFactorListener (this); | |||||
| nativeScaleFactor = (float) currentPeer->getPlatformScaleFactor(); | |||||
| } | |||||
| /* Convert from the hosted VST3's coordinate system to the component's coordinate system. */ | |||||
| Rectangle<int> vst3ToComponentRect (const ViewRect& vr) const | |||||
| { | |||||
| return getLocalArea (nullptr, Rectangle<int> { vr.right, vr.bottom } / (nativeScaleFactor * getDesktopScaleFactor())); | |||||
| } | } | ||||
| void componentMovedOrResized (bool, bool wasResized) override | void componentMovedOrResized (bool, bool wasResized) override | ||||
| @@ -1438,44 +1438,34 @@ private: | |||||
| if (recursiveResize || ! wasResized || getTopLevelComponent()->getPeer() == nullptr) | if (recursiveResize || ! wasResized || getTopLevelComponent()->getPeer() == nullptr) | ||||
| return; | return; | ||||
| ViewRect rect; | |||||
| if (view->canResize() == kResultTrue) | if (view->canResize() == kResultTrue) | ||||
| { | { | ||||
| rect.right = (Steinberg::int32) roundToInt ((float) getWidth() * nativeScaleFactor); | |||||
| rect.bottom = (Steinberg::int32) roundToInt ((float) getHeight() * nativeScaleFactor); | |||||
| auto rect = componentToVST3Rect (getLocalBounds()); | |||||
| view->checkSizeConstraint (&rect); | view->checkSizeConstraint (&rect); | ||||
| { | { | ||||
| const ScopedValueSetter<bool> recursiveResizeSetter (recursiveResize, true); | const ScopedValueSetter<bool> recursiveResizeSetter (recursiveResize, true); | ||||
| setSize (roundToInt ((float) rect.getWidth() / nativeScaleFactor), | |||||
| roundToInt ((float) rect.getHeight() / nativeScaleFactor)); | |||||
| const auto logicalSize = vst3ToComponentRect (rect); | |||||
| setSize (logicalSize.getWidth(), logicalSize.getHeight()); | |||||
| } | } | ||||
| #if JUCE_WINDOWS | |||||
| setPluginWindowPos (rect); | |||||
| #else | |||||
| embeddedComponent.setBounds (getLocalBounds()); | embeddedComponent.setBounds (getLocalBounds()); | ||||
| #endif | |||||
| view->onSize (&rect); | view->onSize (&rect); | ||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| ViewRect rect; | |||||
| warnOnFailure (view->getSize (&rect)); | warnOnFailure (view->getSize (&rect)); | ||||
| #if JUCE_WINDOWS | |||||
| setPluginWindowPos (rect); | |||||
| #else | |||||
| resizeWithRect (embeddedComponent, rect, nativeScaleFactor); | |||||
| #endif | |||||
| resizeWithRect (embeddedComponent, rect); | |||||
| } | } | ||||
| // Some plugins don't update their cursor correctly when mousing out the window | // Some plugins don't update their cursor correctly when mousing out the window | ||||
| Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); | Desktop::getInstance().getMainMouseSource().forceMouseCursorUpdate(); | ||||
| } | } | ||||
| using ComponentMovementWatcher::componentMovedOrResized; | using ComponentMovementWatcher::componentMovedOrResized; | ||||
| void componentVisibilityChanged() override | void componentVisibilityChanged() override | ||||
| @@ -1484,20 +1474,14 @@ private: | |||||
| resizeToFit(); | resizeToFit(); | ||||
| componentMovedOrResized (true, true); | componentMovedOrResized (true, true); | ||||
| } | } | ||||
| using ComponentMovementWatcher::componentVisibilityChanged; | |||||
| void nativeScaleFactorChanged (double newScaleFactor) override | |||||
| { | |||||
| nativeScaleFactor = (float) newScaleFactor; | |||||
| updatePluginScale(); | |||||
| componentMovedOrResized (false, true); | |||||
| } | |||||
| using ComponentMovementWatcher::componentVisibilityChanged; | |||||
| void resizeToFit() | void resizeToFit() | ||||
| { | { | ||||
| ViewRect rect; | ViewRect rect; | ||||
| warnOnFailure (view->getSize (&rect)); | warnOnFailure (view->getSize (&rect)); | ||||
| resizeWithRect (*this, rect, nativeScaleFactor); | |||||
| resizeWithRect (*this, rect); | |||||
| } | } | ||||
| tresult PLUGIN_API resizeView (IPlugView* incomingView, ViewRect* newSize) override | tresult PLUGIN_API resizeView (IPlugView* incomingView, ViewRect* newSize) override | ||||
| @@ -1506,34 +1490,28 @@ private: | |||||
| if (incomingView != nullptr && newSize != nullptr && incomingView == view) | if (incomingView != nullptr && newSize != nullptr && incomingView == view) | ||||
| { | { | ||||
| auto scaleToViewRect = [this] (int dimension) | |||||
| { | |||||
| return (Steinberg::int32) roundToInt ((float) dimension * nativeScaleFactor); | |||||
| }; | |||||
| auto oldWidth = scaleToViewRect (getWidth()); | |||||
| auto oldHeight = scaleToViewRect (getHeight()); | |||||
| resizeWithRect (embeddedComponent, *newSize, nativeScaleFactor); | |||||
| const auto oldPhysicalSize = componentToVST3Rect (getLocalBounds()); | |||||
| const auto logicalSize = vst3ToComponentRect (*newSize); | |||||
| setSize (logicalSize.getWidth(), logicalSize.getHeight()); | |||||
| embeddedComponent.setSize (logicalSize.getWidth(), logicalSize.getHeight()); | |||||
| #if JUCE_WINDOWS | #if JUCE_WINDOWS | ||||
| setPluginWindowPos (*newSize); | |||||
| embeddedComponent.updateHWNDBounds(); | |||||
| #elif JUCE_LINUX || JUCE_BSD | |||||
| embeddedComponent.updateEmbeddedBounds(); | |||||
| #endif | #endif | ||||
| setSize (embeddedComponent.getWidth(), embeddedComponent.getHeight()); | |||||
| // According to the VST3 Workflow Diagrams, a resizeView from the plugin should | // According to the VST3 Workflow Diagrams, a resizeView from the plugin should | ||||
| // always trigger a response from the host which confirms the new size. | // always trigger a response from the host which confirms the new size. | ||||
| ViewRect rect { 0, 0, | |||||
| scaleToViewRect (getWidth()), | |||||
| scaleToViewRect (getHeight()) }; | |||||
| auto currentPhysicalSize = componentToVST3Rect (getLocalBounds()); | |||||
| if (rect.right != oldWidth || rect.bottom != oldHeight | |||||
| if (currentPhysicalSize.getWidth() != oldPhysicalSize.getWidth() | |||||
| || currentPhysicalSize.getHeight() != oldPhysicalSize.getHeight() | |||||
| || ! isInOnSize) | || ! isInOnSize) | ||||
| { | { | ||||
| // Guard against plug-ins immediately calling resizeView() with the same size | // Guard against plug-ins immediately calling resizeView() with the same size | ||||
| const ScopedValueSetter<bool> inOnSizeSetter (isInOnSize, true); | const ScopedValueSetter<bool> inOnSizeSetter (isInOnSize, true); | ||||
| view->onSize (&rect); | |||||
| view->onSize (¤tPhysicalSize); | |||||
| } | } | ||||
| return kResultTrue; | return kResultTrue; | ||||
| @@ -1544,10 +1522,11 @@ private: | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| static void resizeWithRect (Component& comp, const ViewRect& rect, float scaleFactor) | |||||
| void resizeWithRect (Component& comp, const ViewRect& rect) const | |||||
| { | { | ||||
| comp.setSize (jmax (10, std::abs (roundToInt ((float) rect.getWidth() / scaleFactor))), | |||||
| jmax (10, std::abs (roundToInt ((float) rect.getHeight() / scaleFactor)))); | |||||
| const auto logicalSize = vst3ToComponentRect (rect); | |||||
| comp.setSize (jmax (10, logicalSize.getWidth()), | |||||
| jmax (10, logicalSize.getHeight())); | |||||
| } | } | ||||
| void attachPluginWindow() | void attachPluginWindow() | ||||
| @@ -1555,19 +1534,16 @@ private: | |||||
| if (pluginHandle == HandleFormat{}) | if (pluginHandle == HandleFormat{}) | ||||
| { | { | ||||
| #if JUCE_WINDOWS | #if JUCE_WINDOWS | ||||
| if (auto* topComp = getTopLevelComponent()) | |||||
| { | |||||
| peer.reset (embeddedComponent.createNewPeer (0, topComp->getWindowHandle())); | |||||
| pluginHandle = (HandleFormat) peer->getNativeHandle(); | |||||
| } | |||||
| #else | |||||
| pluginHandle = static_cast<HWND> (embeddedComponent.getHWND()); | |||||
| #endif | |||||
| embeddedComponent.setBounds (getLocalBounds()); | embeddedComponent.setBounds (getLocalBounds()); | ||||
| addAndMakeVisible (embeddedComponent); | addAndMakeVisible (embeddedComponent); | ||||
| #if JUCE_MAC | |||||
| pluginHandle = (HandleFormat) embeddedComponent.getView(); | |||||
| #elif JUCE_LINUX || JUCE_BSD | |||||
| pluginHandle = (HandleFormat) embeddedComponent.getHostWindowID(); | |||||
| #endif | |||||
| #if JUCE_MAC | |||||
| pluginHandle = (HandleFormat) embeddedComponent.getView(); | |||||
| #elif JUCE_LINUX || JUCE_BSD | |||||
| pluginHandle = (HandleFormat) embeddedComponent.getHostWindowID(); | |||||
| #endif | #endif | ||||
| if (pluginHandle == HandleFormat{}) | if (pluginHandle == HandleFormat{}) | ||||
| @@ -1586,16 +1562,6 @@ private: | |||||
| } | } | ||||
| } | } | ||||
| void removeScaleFactorListener() | |||||
| { | |||||
| if (currentPeer == nullptr) | |||||
| return; | |||||
| for (int i = 0; i < ComponentPeer::getNumPeers(); ++i) | |||||
| if (ComponentPeer::getPeer (i) == currentPeer) | |||||
| currentPeer->removeScaleFactorListener (this); | |||||
| } | |||||
| void updatePluginScale() | void updatePluginScale() | ||||
| { | { | ||||
| if (scaleInterface != nullptr) | if (scaleInterface != nullptr) | ||||
| @@ -1608,7 +1574,7 @@ private: | |||||
| { | { | ||||
| if (scaleInterface != nullptr) | if (scaleInterface != nullptr) | ||||
| { | { | ||||
| const auto result = scaleInterface->setContentScaleFactor ((Steinberg::IPlugViewContentScaleSupport::ScaleFactor) nativeScaleFactor); | |||||
| const auto result = scaleInterface->setContentScaleFactor ((Steinberg::IPlugViewContentScaleSupport::ScaleFactor) getEffectiveScale()); | |||||
| ignoreUnused (result); | ignoreUnused (result); | ||||
| #if ! JUCE_MAC | #if ! JUCE_MAC | ||||
| @@ -1617,38 +1583,49 @@ private: | |||||
| } | } | ||||
| } | } | ||||
| void setScaleFactor (float s) override | |||||
| { | |||||
| userScaleFactor = s; | |||||
| setContentScaleFactor(); | |||||
| resizeToFit(); | |||||
| } | |||||
| float getEffectiveScale() const | |||||
| { | |||||
| return nativeScaleFactor * userScaleFactor; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| Atomic<int> refCount { 1 }; | Atomic<int> refCount { 1 }; | ||||
| VSTComSmartPtr<IPlugView> view; | VSTComSmartPtr<IPlugView> view; | ||||
| #if JUCE_WINDOWS | #if JUCE_WINDOWS | ||||
| struct ChildComponent : public Component | |||||
| { | |||||
| ChildComponent() { setOpaque (true); } | |||||
| void paint (Graphics& g) override { g.fillAll (Colours::cornflowerblue); } | |||||
| using Component::createNewPeer; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildComponent) | |||||
| }; | |||||
| using HandleFormat = HWND; | |||||
| void setPluginWindowPos (ViewRect rect) | |||||
| struct ViewComponent : public HWNDComponent | |||||
| { | { | ||||
| if (auto* topComp = getTopLevelComponent()) | |||||
| ViewComponent() | |||||
| { | { | ||||
| auto pos = (topComp->getLocalPoint (this, Point<int>()) * nativeScaleFactor).roundToInt(); | |||||
| setOpaque (true); | |||||
| inner.addToDesktop (0); | |||||
| ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { pluginHandle }; | |||||
| SetWindowPos (pluginHandle, nullptr, | |||||
| pos.x, pos.y, | |||||
| rect.getWidth(), rect.getHeight(), | |||||
| isVisible() ? SWP_SHOWWINDOW : SWP_HIDEWINDOW); | |||||
| if (auto* peer = inner.getPeer()) | |||||
| setHWND (peer->getNativeHandle()); | |||||
| } | } | ||||
| } | |||||
| ChildComponent embeddedComponent; | |||||
| std::unique_ptr<ComponentPeer> peer; | |||||
| using HandleFormat = HWND; | |||||
| void paint (Graphics& g) override { g.fillAll (Colours::black); } | |||||
| private: | |||||
| struct Inner : public Component | |||||
| { | |||||
| Inner() { setOpaque (true); } | |||||
| void paint (Graphics& g) override { g.fillAll (Colours::black); } | |||||
| }; | |||||
| Inner inner; | |||||
| }; | |||||
| ViewComponent embeddedComponent; | |||||
| #elif JUCE_MAC | #elif JUCE_MAC | ||||
| NSViewComponentWithParent embeddedComponent; | NSViewComponentWithParent embeddedComponent; | ||||
| using HandleFormat = NSView*; | using HandleFormat = NSView*; | ||||
| @@ -1664,9 +1641,35 @@ private: | |||||
| HandleFormat pluginHandle = {}; | HandleFormat pluginHandle = {}; | ||||
| bool recursiveResize = false, isInOnSize = false, attachedCalled = false; | bool recursiveResize = false, isInOnSize = false, attachedCalled = false; | ||||
| ComponentPeer* currentPeer = nullptr; | |||||
| Steinberg::IPlugViewContentScaleSupport* scaleInterface = nullptr; | Steinberg::IPlugViewContentScaleSupport* scaleInterface = nullptr; | ||||
| float nativeScaleFactor = 1.0f; | float nativeScaleFactor = 1.0f; | ||||
| float userScaleFactor = 1.0f; | |||||
| struct ScaleNotifierCallback | |||||
| { | |||||
| VST3PluginWindow& window; | |||||
| void operator() (float platformScale) const | |||||
| { | |||||
| MessageManager::callAsync ([ref = Component::SafePointer<VST3PluginWindow> (&window), platformScale] | |||||
| { | |||||
| if (auto* r = ref.getComponent()) | |||||
| { | |||||
| r->nativeScaleFactor = platformScale; | |||||
| r->setContentScaleFactor(); | |||||
| r->resizeToFit(); | |||||
| #if JUCE_WINDOWS | |||||
| r->embeddedComponent.updateHWNDBounds(); | |||||
| #elif JUCE_LINUX || JUCE_BSD | |||||
| r->embeddedComponent.updateEmbeddedBounds(); | |||||
| #endif | |||||
| } | |||||
| }); | |||||
| } | |||||
| }; | |||||
| NativeScaleFactorNotifier scaleNotifier { this, ScaleNotifierCallback { *this } }; | |||||
| //============================================================================== | //============================================================================== | ||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginWindow) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginWindow) | ||||
| @@ -2206,32 +2209,7 @@ public: | |||||
| ~VST3PluginInstance() override | ~VST3PluginInstance() override | ||||
| { | { | ||||
| struct VST3Deleter : public CallbackMessage | |||||
| { | |||||
| VST3Deleter (VST3PluginInstance& inInstance, WaitableEvent& inEvent) | |||||
| : vst3Instance (inInstance), completionSignal (inEvent) | |||||
| {} | |||||
| void messageCallback() override | |||||
| { | |||||
| vst3Instance.cleanup(); | |||||
| completionSignal.signal(); | |||||
| } | |||||
| VST3PluginInstance& vst3Instance; | |||||
| WaitableEvent& completionSignal; | |||||
| }; | |||||
| if (MessageManager::getInstance()->isThisTheMessageThread()) | |||||
| { | |||||
| cleanup(); | |||||
| } | |||||
| else | |||||
| { | |||||
| WaitableEvent completionEvent; | |||||
| (new VST3Deleter (*this, completionEvent))->post(); | |||||
| completionEvent.wait(); | |||||
| } | |||||
| callOnMessageThread ([this] { cleanup(); }); | |||||
| } | } | ||||
| void cleanup() | void cleanup() | ||||
| @@ -2988,7 +2966,6 @@ public: | |||||
| ignoreUnused (data, sizeInBytes); | ignoreUnused (data, sizeInBytes); | ||||
| } | } | ||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| #if JUCE_LINUX || JUCE_BSD | #if JUCE_LINUX || JUCE_BSD | ||||
| @@ -3318,7 +3295,7 @@ private: | |||||
| /** @note An IPlugView, when first created, should start with a ref-count of 1! */ | /** @note An IPlugView, when first created, should start with a ref-count of 1! */ | ||||
| IPlugView* tryCreatingView() const | IPlugView* tryCreatingView() const | ||||
| { | { | ||||
| JUCE_ASSERT_MESSAGE_THREAD | |||||
| JUCE_ASSERT_MESSAGE_MANAGER_IS_LOCKED | |||||
| IPlugView* v = editController->createView (Vst::ViewType::kEditor); | IPlugView* v = editController->createView (Vst::ViewType::kEditor); | ||||
| @@ -1083,34 +1083,7 @@ struct VSTPluginInstance final : public AudioPluginInstance, | |||||
| ~VSTPluginInstance() override | ~VSTPluginInstance() override | ||||
| { | { | ||||
| if (vstEffect != nullptr && vstEffect->magic == 0x56737450 /* 'VstP' */) | if (vstEffect != nullptr && vstEffect->magic == 0x56737450 /* 'VstP' */) | ||||
| { | |||||
| struct VSTDeleter : public CallbackMessage | |||||
| { | |||||
| VSTDeleter (VSTPluginInstance& inInstance, WaitableEvent& inEvent) | |||||
| : vstInstance (inInstance), completionSignal (inEvent) | |||||
| {} | |||||
| void messageCallback() override | |||||
| { | |||||
| vstInstance.cleanup(); | |||||
| completionSignal.signal(); | |||||
| } | |||||
| VSTPluginInstance& vstInstance; | |||||
| WaitableEvent& completionSignal; | |||||
| }; | |||||
| if (MessageManager::getInstance()->isThisTheMessageThread()) | |||||
| { | |||||
| cleanup(); | |||||
| } | |||||
| else | |||||
| { | |||||
| WaitableEvent completionEvent; | |||||
| (new VSTDeleter (*this, completionEvent))->post(); | |||||
| completionEvent.wait(); | |||||
| } | |||||
| } | |||||
| callOnMessageThread ([this] { cleanup(); }); | |||||
| } | } | ||||
| void cleanup() | void cleanup() | ||||
| @@ -1982,20 +1955,7 @@ struct VSTPluginInstance final : public AudioPluginInstance, | |||||
| return false; | return false; | ||||
| } | } | ||||
| bool updateSizeFromEditor (int w, int h) | |||||
| { | |||||
| editorSize = { w, h }; | |||||
| if (auto* editor = getActiveEditor()) | |||||
| { | |||||
| editor->setSize (w, h); | |||||
| return true; | |||||
| } | |||||
| return false; | |||||
| } | |||||
| Rectangle<int> getEditorSize() const { return editorSize; } | |||||
| bool updateSizeFromEditor (int w, int h); | |||||
| Vst2::AEffect* vstEffect; | Vst2::AEffect* vstEffect; | ||||
| ModuleHandle::Ptr vstModule; | ModuleHandle::Ptr vstModule; | ||||
| @@ -2076,7 +2036,6 @@ private: | |||||
| AudioBuffer<double> tmpBufferDouble; | AudioBuffer<double> tmpBufferDouble; | ||||
| HeapBlock<double*> channelBufferDouble; | HeapBlock<double*> channelBufferDouble; | ||||
| std::unique_ptr<VST2BypassParameter> bypassParam; | std::unique_ptr<VST2BypassParameter> bypassParam; | ||||
| Rectangle<int> editorSize; | |||||
| std::unique_ptr<VSTXMLInfo> xmlInfo; | std::unique_ptr<VSTXMLInfo> xmlInfo; | ||||
| @@ -2141,24 +2100,11 @@ private: | |||||
| void setWindowSize (int width, int height) | void setWindowSize (int width, int height) | ||||
| { | { | ||||
| if (auto* ed = getActiveEditor()) | |||||
| { | |||||
| #if JUCE_LINUX || JUCE_BSD | |||||
| const MessageManagerLock mmLock; | |||||
| #endif | |||||
| #if ! JUCE_MAC | |||||
| if (auto* peer = ed->getTopLevelComponent()->getPeer()) | |||||
| { | |||||
| auto scale = peer->getPlatformScaleFactor(); | |||||
| updateSizeFromEditor (roundToInt (width / scale), roundToInt (height / scale)); | |||||
| return; | |||||
| } | |||||
| #endif | |||||
| #if JUCE_LINUX || JUCE_BSD | |||||
| const MessageManagerLock mmLock; | |||||
| #endif | |||||
| updateSizeFromEditor (width, height); | |||||
| } | |||||
| updateSizeFromEditor (width, height); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -2769,7 +2715,6 @@ static Array<VSTPluginWindow*> activeVSTWindows; | |||||
| struct VSTPluginWindow : public AudioProcessorEditor, | struct VSTPluginWindow : public AudioProcessorEditor, | ||||
| #if ! JUCE_MAC | #if ! JUCE_MAC | ||||
| private ComponentMovementWatcher, | private ComponentMovementWatcher, | ||||
| private ComponentPeer::ScaleFactorListener, | |||||
| #endif | #endif | ||||
| private Timer | private Timer | ||||
| { | { | ||||
| @@ -2813,6 +2758,10 @@ public: | |||||
| setOpaque (true); | setOpaque (true); | ||||
| setVisible (true); | setVisible (true); | ||||
| #if JUCE_WINDOWS | |||||
| addAndMakeVisible (embeddedComponent); | |||||
| #endif | |||||
| } | } | ||||
| ~VSTPluginWindow() override | ~VSTPluginWindow() override | ||||
| @@ -2824,8 +2773,6 @@ public: | |||||
| carbonWrapper.reset(); | carbonWrapper.reset(); | ||||
| #endif | #endif | ||||
| cocoaWrapper.reset(); | cocoaWrapper.reset(); | ||||
| #else | |||||
| removeScaleFactorListeners(); | |||||
| #endif | #endif | ||||
| activeVSTWindows.removeFirstMatchingValue (this); | activeVSTWindows.removeFirstMatchingValue (this); | ||||
| @@ -2833,10 +2780,36 @@ public: | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| void updateSizeFromEditor (int w, int h) | |||||
| /* Convert from the hosted VST's coordinate system to the component's coordinate system. */ | |||||
| Rectangle<int> vstToComponentRect (Component& editor, const Rectangle<int>& vr) const | |||||
| { | { | ||||
| if (! plugin.updateSizeFromEditor (w, h)) | |||||
| setSize (w, h); | |||||
| return editor.getLocalArea (nullptr, vr / (nativeScaleFactor * getDesktopScaleFactor())); | |||||
| } | |||||
| Rectangle<int> componentToVstRect (Component& editor, const Rectangle<int>& vr) const | |||||
| { | |||||
| if (auto* tl = editor.getTopLevelComponent()) | |||||
| return tl->getLocalArea (&editor, vr) * nativeScaleFactor * tl->getDesktopScaleFactor(); | |||||
| return {}; | |||||
| } | |||||
| bool updateSizeFromEditor (int w, int h) | |||||
| { | |||||
| const auto correctedBounds = vstToComponentRect (*this, { w, h }); | |||||
| setSize (correctedBounds.getWidth(), correctedBounds.getHeight()); | |||||
| #if JUCE_MAC | |||||
| #if JUCE_SUPPORT_CARBON | |||||
| if (carbonWrapper != nullptr) | |||||
| carbonWrapper->setSize (correctedBounds.getWidth(), correctedBounds.getHeight()); | |||||
| #endif | |||||
| if (cocoaWrapper != nullptr) | |||||
| cocoaWrapper->setSize (correctedBounds.getWidth(), correctedBounds.getHeight()); | |||||
| #endif | |||||
| return true; | |||||
| } | } | ||||
| #if JUCE_MAC | #if JUCE_MAC | ||||
| @@ -2870,6 +2843,11 @@ public: | |||||
| void parentHierarchyChanged() override { visibilityChanged(); } | void parentHierarchyChanged() override { visibilityChanged(); } | ||||
| #else | #else | ||||
| float getEffectiveScale() const | |||||
| { | |||||
| return nativeScaleFactor * userScaleFactor; | |||||
| } | |||||
| void paint (Graphics& g) override | void paint (Graphics& g) override | ||||
| { | { | ||||
| #if JUCE_LINUX || JUCE_BSD | #if JUCE_LINUX || JUCE_BSD | ||||
| @@ -2877,7 +2855,7 @@ public: | |||||
| { | { | ||||
| if (pluginWindow != 0) | if (pluginWindow != 0) | ||||
| { | { | ||||
| auto clip = g.getClipBounds(); | |||||
| auto clip = componentToVstRect (*this, g.getClipBounds().toNearestInt()); | |||||
| X11Symbols::getInstance()->xClearArea (display, pluginWindow, clip.getX(), clip.getY(), | X11Symbols::getInstance()->xClearArea (display, pluginWindow, clip.getX(), clip.getY(), | ||||
| static_cast<unsigned int> (clip.getWidth()), | static_cast<unsigned int> (clip.getWidth()), | ||||
| @@ -2896,36 +2874,15 @@ public: | |||||
| if (recursiveResize) | if (recursiveResize) | ||||
| return; | return; | ||||
| if (auto* peer = getTopLevelComponent()->getPeer()) | |||||
| if (getPeer() != nullptr) | |||||
| { | { | ||||
| const ScopedValueSetter<bool> recursiveResizeSetter (recursiveResize, true); | const ScopedValueSetter<bool> recursiveResizeSetter (recursiveResize, true); | ||||
| const auto pos = (peer->getAreaCoveredBy (*this).toFloat() * nativeScaleFactor).toNearestInt(); | |||||
| #if JUCE_WINDOWS | #if JUCE_WINDOWS | ||||
| if (pluginHWND != 0) | |||||
| { | |||||
| ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { pluginHWND }; | |||||
| SetWindowPos (pluginHWND, | |||||
| HWND_BOTTOM, | |||||
| pos.getX(), | |||||
| pos.getY(), | |||||
| 0, | |||||
| 0, | |||||
| SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER); | |||||
| MessageManager::callAsync ([ref = SafePointer<Component> (this)] | |||||
| { | |||||
| // Clean up after the editor window, in case it tried to move itself | |||||
| // into the wrong location over this component. | |||||
| if (ref == nullptr) | |||||
| return; | |||||
| if (auto* p = ref->getPeer()) | |||||
| p->repaint (p->getBounds().withPosition ({})); | |||||
| }); | |||||
| } | |||||
| embeddedComponent.setBounds (getLocalBounds()); | |||||
| #elif JUCE_LINUX || JUCE_BSD | #elif JUCE_LINUX || JUCE_BSD | ||||
| const auto pos = componentToVstRect (*this, getLocalBounds()); | |||||
| if (pluginWindow != 0) | if (pluginWindow != 0) | ||||
| { | { | ||||
| auto* symbols = X11Symbols::getInstance(); | auto* symbols = X11Symbols::getInstance(); | ||||
| @@ -2951,8 +2908,7 @@ public: | |||||
| else if (! shouldAvoidDeletingWindow()) | else if (! shouldAvoidDeletingWindow()) | ||||
| closePluginWindow(); | closePluginWindow(); | ||||
| if (auto* peer = getTopLevelComponent()->getPeer()) | |||||
| setScaleFactorAndDispatchMessage (peer->getPlatformScaleFactor()); | |||||
| setContentScaleFactor(); | |||||
| #if JUCE_LINUX || JUCE_BSD | #if JUCE_LINUX || JUCE_BSD | ||||
| MessageManager::callAsync ([safeThis = SafePointer<VSTPluginWindow> { this }] | MessageManager::callAsync ([safeThis = SafePointer<VSTPluginWindow> { this }] | ||||
| @@ -2971,38 +2927,32 @@ public: | |||||
| { | { | ||||
| closePluginWindow(); | closePluginWindow(); | ||||
| openPluginWindow(); | openPluginWindow(); | ||||
| removeScaleFactorListeners(); | |||||
| if (auto* peer = getTopLevelComponent()->getPeer()) | |||||
| peer->addScaleFactorListener (this); | |||||
| componentMovedOrResized (true, true); | componentMovedOrResized (true, true); | ||||
| } | } | ||||
| void nativeScaleFactorChanged (double newScaleFactor) override | |||||
| void setContentScaleFactor() | |||||
| { | { | ||||
| setScaleFactorAndDispatchMessage (newScaleFactor); | |||||
| #if JUCE_WINDOWS | |||||
| resizeToFit(); | |||||
| #endif | |||||
| } | |||||
| void setScaleFactorAndDispatchMessage (double newScaleFactor) | |||||
| { | |||||
| if (approximatelyEqual ((float) newScaleFactor, nativeScaleFactor)) | |||||
| return; | |||||
| nativeScaleFactor = (float) newScaleFactor; | |||||
| if (pluginRespondsToDPIChanges) | if (pluginRespondsToDPIChanges) | ||||
| dispatch (Vst2::effVendorSpecific, | dispatch (Vst2::effVendorSpecific, | ||||
| (int) ByteOrder::bigEndianInt ("PreS"), | (int) ByteOrder::bigEndianInt ("PreS"), | ||||
| (int) ByteOrder::bigEndianInt ("AeCs"), | (int) ByteOrder::bigEndianInt ("AeCs"), | ||||
| nullptr, nativeScaleFactor); | |||||
| nullptr, getEffectiveScale()); | |||||
| } | } | ||||
| #endif | #endif | ||||
| void setScaleFactor (float scale) override | |||||
| { | |||||
| userScaleFactor = scale; | |||||
| #if ! JUCE_MAC | |||||
| setContentScaleFactor(); | |||||
| #endif | |||||
| #if JUCE_WINDOWS | |||||
| resizeToFit(); | |||||
| #endif | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| bool keyStateChanged (bool) override { return pluginWantsKeys; } | bool keyStateChanged (bool) override { return pluginWantsKeys; } | ||||
| bool keyPressed (const juce::KeyPress&) override { return pluginWantsKeys; } | bool keyPressed (const juce::KeyPress&) override { return pluginWantsKeys; } | ||||
| @@ -3025,7 +2975,14 @@ public: | |||||
| if (! reentrantGuard) | if (! reentrantGuard) | ||||
| { | { | ||||
| reentrantGuard = true; | reentrantGuard = true; | ||||
| #if JUCE_WINDOWS | |||||
| // Some plugins may draw/resize inside their idle callback, so ensure that | |||||
| // DPI awareness is set correctly inside this call. | |||||
| ScopedThreadDPIAwarenessSetter scope (getPluginHWND()); | |||||
| #endif | |||||
| plugin.dispatch (Vst2::effEditIdle, 0, 0, nullptr, 0); | plugin.dispatch (Vst2::effEditIdle, 0, 0, nullptr, 0); | ||||
| reentrantGuard = false; | reentrantGuard = false; | ||||
| } | } | ||||
| @@ -3124,7 +3081,7 @@ private: | |||||
| #else | #else | ||||
| void openPluginWindow() | void openPluginWindow() | ||||
| { | { | ||||
| if (isOpen || getWindowHandle() == nullptr) | |||||
| if (isOpen) | |||||
| return; | return; | ||||
| JUCE_VST_LOG ("Opening VST UI: " + plugin.getName()); | JUCE_VST_LOG ("Opening VST UI: " + plugin.getName()); | ||||
| @@ -3132,13 +3089,18 @@ private: | |||||
| pluginRespondsToDPIChanges = plugin.pluginCanDo ("supportsViewDpiScaling") > 0; | pluginRespondsToDPIChanges = plugin.pluginCanDo ("supportsViewDpiScaling") > 0; | ||||
| if (auto* peer = getTopLevelComponent()->getPeer()) | |||||
| setScaleFactorAndDispatchMessage (peer->getPlatformScaleFactor()); | |||||
| setContentScaleFactor(); | |||||
| Vst2::ERect* rect = nullptr; | Vst2::ERect* rect = nullptr; | ||||
| dispatch (Vst2::effEditGetRect, 0, 0, &rect, 0); | dispatch (Vst2::effEditGetRect, 0, 0, &rect, 0); | ||||
| dispatch (Vst2::effEditOpen, 0, 0, getWindowHandle(), 0); | |||||
| #if JUCE_WINDOWS | |||||
| auto* handle = embeddedComponent.getHWND(); | |||||
| #else | |||||
| auto* handle = getWindowHandle(); | |||||
| #endif | |||||
| dispatch (Vst2::effEditOpen, 0, 0, handle, 0); | |||||
| dispatch (Vst2::effEditGetRect, 0, 0, &rect, 0); // do this before and after like in the steinberg example | dispatch (Vst2::effEditGetRect, 0, 0, &rect, 0); // do this before and after like in the steinberg example | ||||
| dispatch (Vst2::effGetProgram, 0, 0, nullptr, 0); // also in steinberg code | dispatch (Vst2::effGetProgram, 0, 0, nullptr, 0); // also in steinberg code | ||||
| @@ -3146,7 +3108,7 @@ private: | |||||
| #if JUCE_WINDOWS | #if JUCE_WINDOWS | ||||
| originalWndProc = 0; | originalWndProc = 0; | ||||
| pluginHWND = GetWindow ((HWND) getWindowHandle(), GW_CHILD); | |||||
| auto* pluginHWND = getPluginHWND(); | |||||
| if (pluginHWND == 0) | if (pluginHWND == 0) | ||||
| { | { | ||||
| @@ -3189,7 +3151,7 @@ private: | |||||
| ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { pluginHWND }; | ScopedThreadDPIAwarenessSetter threadDpiAwarenessSetter { pluginHWND }; | ||||
| SetWindowPos (pluginHWND, 0, | SetWindowPos (pluginHWND, 0, | ||||
| 0, 0, roundToInt (rw * nativeScaleFactor), roundToInt (rh * nativeScaleFactor), | |||||
| 0, 0, rw, rh, | |||||
| SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); | ||||
| GetWindowRect (pluginHWND, &r); | GetWindowRect (pluginHWND, &r); | ||||
| @@ -3225,9 +3187,6 @@ private: | |||||
| X11Symbols::getInstance()->xMapRaised (display, pluginWindow); | X11Symbols::getInstance()->xMapRaised (display, pluginWindow); | ||||
| #endif | #endif | ||||
| w = roundToInt ((float) w / nativeScaleFactor); | |||||
| h = roundToInt ((float) h / nativeScaleFactor); | |||||
| // double-check it's not too tiny | // double-check it's not too tiny | ||||
| w = jmax (w, 32); | w = jmax (w, 32); | ||||
| h = jmax (h, 32); | h = jmax (h, 32); | ||||
| @@ -3241,13 +3200,6 @@ private: | |||||
| startTimer (18 + juce::Random::getSystemRandom().nextInt (5)); | startTimer (18 + juce::Random::getSystemRandom().nextInt (5)); | ||||
| repaint(); | repaint(); | ||||
| } | } | ||||
| void removeScaleFactorListeners() | |||||
| { | |||||
| for (int i = 0; i < ComponentPeer::getNumPeers(); ++i) | |||||
| if (auto* peer = ComponentPeer::getPeer (i)) | |||||
| peer->removeScaleFactorListener (this); | |||||
| } | |||||
| #endif | #endif | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -3266,12 +3218,13 @@ private: | |||||
| #if JUCE_WINDOWS | #if JUCE_WINDOWS | ||||
| JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4244) | JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4244) | ||||
| auto* pluginHWND = getPluginHWND(); | |||||
| if (originalWndProc != 0 && pluginHWND != 0 && IsWindow (pluginHWND)) | if (originalWndProc != 0 && pluginHWND != 0 && IsWindow (pluginHWND)) | ||||
| SetWindowLongPtr (pluginHWND, GWLP_WNDPROC, (LONG_PTR) originalWndProc); | SetWindowLongPtr (pluginHWND, GWLP_WNDPROC, (LONG_PTR) originalWndProc); | ||||
| JUCE_END_IGNORE_WARNINGS_MSVC | JUCE_END_IGNORE_WARNINGS_MSVC | ||||
| originalWndProc = 0; | originalWndProc = 0; | ||||
| pluginHWND = 0; | |||||
| #elif JUCE_LINUX || JUCE_BSD | #elif JUCE_LINUX || JUCE_BSD | ||||
| pluginWindow = 0; | pluginWindow = 0; | ||||
| #endif | #endif | ||||
| @@ -3291,7 +3244,8 @@ private: | |||||
| if (pluginRefusesToResize) | if (pluginRefusesToResize) | ||||
| return true; | return true; | ||||
| return (isWithin (w, getWidth(), 5) && isWithin (h, getHeight(), 5)); | |||||
| const auto converted = vstToComponentRect (*this, { w, h }); | |||||
| return (isWithin (converted.getWidth(), getWidth(), 5) && isWithin (converted.getHeight(), getHeight(), 5)); | |||||
| } | } | ||||
| void resizeToFit() | void resizeToFit() | ||||
| @@ -3299,14 +3253,16 @@ private: | |||||
| Vst2::ERect* rect = nullptr; | Vst2::ERect* rect = nullptr; | ||||
| dispatch (Vst2::effEditGetRect, 0, 0, &rect, 0); | dispatch (Vst2::effEditGetRect, 0, 0, &rect, 0); | ||||
| auto w = roundToInt ((rect->right - rect->left) / nativeScaleFactor); | |||||
| auto h = roundToInt ((rect->bottom - rect->top) / nativeScaleFactor); | |||||
| auto w = rect->right - rect->left; | |||||
| auto h = rect->bottom - rect->top; | |||||
| if (! isWindowSizeCorrectForPlugin (w, h)) | if (! isWindowSizeCorrectForPlugin (w, h)) | ||||
| { | { | ||||
| updateSizeFromEditor (w, h); | updateSizeFromEditor (w, h); | ||||
| sizeCheckCount = 0; | sizeCheckCount = 0; | ||||
| } | } | ||||
| embeddedComponent.updateHWNDBounds(); | |||||
| } | } | ||||
| void checkPluginWindowSize() | void checkPluginWindowSize() | ||||
| @@ -3322,7 +3278,9 @@ private: | |||||
| { | { | ||||
| Component::SafePointer<VSTPluginWindow> w (activeVSTWindows[i]); | Component::SafePointer<VSTPluginWindow> w (activeVSTWindows[i]); | ||||
| if (w != nullptr && w->pluginHWND == hW) | |||||
| auto* pluginHWND = w->getPluginHWND(); | |||||
| if (w != nullptr && pluginHWND == hW) | |||||
| { | { | ||||
| if (message == WM_CHAR | if (message == WM_CHAR | ||||
| || message == WM_KEYDOWN | || message == WM_KEYDOWN | ||||
| @@ -3337,7 +3295,7 @@ private: | |||||
| if (w != nullptr) // (may have been deleted in SendMessage callback) | if (w != nullptr) // (may have been deleted in SendMessage callback) | ||||
| return CallWindowProc ((WNDPROC) w->originalWndProc, | return CallWindowProc ((WNDPROC) w->originalWndProc, | ||||
| (HWND) w->pluginHWND, | |||||
| (HWND) pluginHWND, | |||||
| message, wParam, lParam); | message, wParam, lParam); | ||||
| } | } | ||||
| } | } | ||||
| @@ -3439,32 +3397,82 @@ private: | |||||
| void resized() override | void resized() override | ||||
| { | { | ||||
| #if JUCE_SUPPORT_CARBON | |||||
| if (carbonWrapper != nullptr) | |||||
| carbonWrapper->setSize (getWidth(), getHeight()); | |||||
| #endif | |||||
| if (cocoaWrapper != nullptr) | |||||
| cocoaWrapper->setSize (getWidth(), getHeight()); | |||||
| } | } | ||||
| #endif | #endif | ||||
| //============================================================================== | //============================================================================== | ||||
| VSTPluginInstance& plugin; | VSTPluginInstance& plugin; | ||||
| float userScaleFactor = 1.0f; | |||||
| bool isOpen = false, recursiveResize = false; | bool isOpen = false, recursiveResize = false; | ||||
| bool pluginWantsKeys = false, pluginRefusesToResize = false, alreadyInside = false; | bool pluginWantsKeys = false, pluginRefusesToResize = false, alreadyInside = false; | ||||
| #if ! JUCE_MAC | #if ! JUCE_MAC | ||||
| bool pluginRespondsToDPIChanges = false; | bool pluginRespondsToDPIChanges = false; | ||||
| float nativeScaleFactor = 1.0f; | float nativeScaleFactor = 1.0f; | ||||
| struct ScaleNotifierCallback | |||||
| { | |||||
| VSTPluginWindow& window; | |||||
| void operator() (float platformScale) const | |||||
| { | |||||
| MessageManager::callAsync ([ref = Component::SafePointer<VSTPluginWindow> (&window), platformScale] | |||||
| { | |||||
| if (auto* r = ref.getComponent()) | |||||
| { | |||||
| r->nativeScaleFactor = platformScale; | |||||
| r->setContentScaleFactor(); | |||||
| #if JUCE_WINDOWS | |||||
| r->resizeToFit(); | |||||
| #endif | |||||
| r->componentMovedOrResized (true, true); | |||||
| } | |||||
| }); | |||||
| } | |||||
| }; | |||||
| NativeScaleFactorNotifier scaleNotifier { this, ScaleNotifierCallback { *this } }; | |||||
| #if JUCE_WINDOWS | #if JUCE_WINDOWS | ||||
| HWND pluginHWND = {}; | |||||
| struct ViewComponent : public HWNDComponent | |||||
| { | |||||
| ViewComponent() | |||||
| { | |||||
| setOpaque (true); | |||||
| inner.addToDesktop (0); | |||||
| if (auto* peer = inner.getPeer()) | |||||
| setHWND (peer->getNativeHandle()); | |||||
| } | |||||
| void paint (Graphics& g) override { g.fillAll (Colours::black); } | |||||
| private: | |||||
| struct Inner : public Component | |||||
| { | |||||
| Inner() { setOpaque (true); } | |||||
| void paint (Graphics& g) override { g.fillAll (Colours::black); } | |||||
| }; | |||||
| Inner inner; | |||||
| }; | |||||
| HWND getPluginHWND() const | |||||
| { | |||||
| return GetWindow ((HWND) embeddedComponent.getHWND(), GW_CHILD); | |||||
| } | |||||
| ViewComponent embeddedComponent; | |||||
| void* originalWndProc = {}; | void* originalWndProc = {}; | ||||
| int sizeCheckCount = 0; | int sizeCheckCount = 0; | ||||
| #elif JUCE_LINUX || JUCE_BSD | #elif JUCE_LINUX || JUCE_BSD | ||||
| ::Display* display = XWindowSystem::getInstance()->getDisplay(); | ::Display* display = XWindowSystem::getInstance()->getDisplay(); | ||||
| Window pluginWindow = 0; | Window pluginWindow = 0; | ||||
| #endif | #endif | ||||
| #else | |||||
| static constexpr auto nativeScaleFactor = 1.0f; | |||||
| #endif | #endif | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -3485,6 +3493,14 @@ AudioProcessorEditor* VSTPluginInstance::createEditor() | |||||
| #endif | #endif | ||||
| } | } | ||||
| bool VSTPluginInstance::updateSizeFromEditor (int w, int h) | |||||
| { | |||||
| if (auto* editor = dynamic_cast<VSTPluginWindow*> (getActiveEditor())) | |||||
| return editor->updateSizeFromEditor (w, h); | |||||
| return false; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| // entry point for all callbacks from the plugin | // entry point for all callbacks from the plugin | ||||
| static pointer_sized_int VSTCALLBACK audioMaster (Vst2::AEffect* effect, int32 opcode, int32 index, pointer_sized_int value, void* ptr, float opt) | static pointer_sized_int VSTCALLBACK audioMaster (Vst2::AEffect* effect, int32 opcode, int32 index, pointer_sized_int value, void* ptr, float opt) | ||||
| @@ -77,6 +77,26 @@ static bool arrayContainsPlugin (const OwnedArray<PluginDescription>& list, | |||||
| #endif | #endif | ||||
| template <typename Callback> | |||||
| void callOnMessageThread (Callback&& callback) | |||||
| { | |||||
| if (MessageManager::getInstance()->existsAndIsLockedByCurrentThread()) | |||||
| { | |||||
| callback(); | |||||
| return; | |||||
| } | |||||
| WaitableEvent completionEvent; | |||||
| MessageManager::callAsync ([&callback, &completionEvent] | |||||
| { | |||||
| callback(); | |||||
| completionEvent.signal(); | |||||
| }); | |||||
| completionEvent.wait(); | |||||
| } | |||||
| #if JUCE_MAC | #if JUCE_MAC | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -203,6 +223,7 @@ private: | |||||
| #include "utilities/juce_ParameterAttachments.cpp" | #include "utilities/juce_ParameterAttachments.cpp" | ||||
| #include "utilities/juce_AudioProcessorValueTreeState.cpp" | #include "utilities/juce_AudioProcessorValueTreeState.cpp" | ||||
| #include "utilities/juce_PluginHostType.cpp" | #include "utilities/juce_PluginHostType.cpp" | ||||
| #include "utilities/juce_NativeScaleFactorNotifier.cpp" | |||||
| #if JUCE_UNIT_TESTS | #if JUCE_UNIT_TESTS | ||||
| #include "format_types/juce_VST3PluginFormat_test.cpp" | #include "format_types/juce_VST3PluginFormat_test.cpp" | ||||
| @@ -111,6 +111,7 @@ | |||||
| //============================================================================== | //============================================================================== | ||||
| #include "utilities/juce_VSTCallbackHandler.h" | #include "utilities/juce_VSTCallbackHandler.h" | ||||
| #include "utilities/juce_VST3ClientExtensions.h" | #include "utilities/juce_VST3ClientExtensions.h" | ||||
| #include "utilities/juce_NativeScaleFactorNotifier.h" | |||||
| #include "utilities/juce_ExtensionsVisitor.h" | #include "utilities/juce_ExtensionsVisitor.h" | ||||
| #include "processors/juce_AudioProcessorParameter.h" | #include "processors/juce_AudioProcessorParameter.h" | ||||
| #include "processors/juce_HostedAudioProcessorParameter.h" | #include "processors/juce_HostedAudioProcessorParameter.h" | ||||
| @@ -0,0 +1,59 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE 7 technical preview. | |||||
| Copyright (c) 2022 - Raw Material Software Limited | |||||
| You may use this code under the terms of the GPL v3 | |||||
| (see www.gnu.org/licenses). | |||||
| For the technical preview this file cannot be licensed commercially. | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| static void removeScaleFactorListenerFromAllPeers (ComponentPeer::ScaleFactorListener& listener) | |||||
| { | |||||
| for (int i = 0; i < ComponentPeer::getNumPeers(); ++i) | |||||
| ComponentPeer::getPeer (i)->removeScaleFactorListener (&listener); | |||||
| } | |||||
| NativeScaleFactorNotifier::NativeScaleFactorNotifier (Component* comp, std::function<void (float)> onScaleChanged) | |||||
| : ComponentMovementWatcher (comp), | |||||
| scaleChanged (std::move (onScaleChanged)) | |||||
| { | |||||
| componentPeerChanged(); | |||||
| } | |||||
| NativeScaleFactorNotifier::~NativeScaleFactorNotifier() | |||||
| { | |||||
| removeScaleFactorListenerFromAllPeers (*this); | |||||
| } | |||||
| void NativeScaleFactorNotifier::nativeScaleFactorChanged (double newScaleFactor) | |||||
| { | |||||
| NullCheckedInvocation::invoke (scaleChanged, (float) newScaleFactor); | |||||
| } | |||||
| void NativeScaleFactorNotifier::componentPeerChanged() | |||||
| { | |||||
| removeScaleFactorListenerFromAllPeers (*this); | |||||
| if (auto* x = getComponent()) | |||||
| peer = x->getPeer(); | |||||
| if (auto* x = peer) | |||||
| { | |||||
| x->addScaleFactorListener (this); | |||||
| nativeScaleFactorChanged (x->getPlatformScaleFactor()); | |||||
| } | |||||
| } | |||||
| } // namespace juce | |||||
| @@ -0,0 +1,60 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE 7 technical preview. | |||||
| Copyright (c) 2022 - Raw Material Software Limited | |||||
| You may use this code under the terms of the GPL v3 | |||||
| (see www.gnu.org/licenses). | |||||
| For the technical preview this file cannot be licensed commercially. | |||||
| JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER | |||||
| EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE | |||||
| DISCLAIMED. | |||||
| ============================================================================== | |||||
| */ | |||||
| namespace juce | |||||
| { | |||||
| /** | |||||
| Calls a function every time the native scale factor of a component's peer changes. | |||||
| This is used in the VST and VST3 wrappers to ensure that the editor's scale is kept in sync with | |||||
| the scale of its containing component. | |||||
| */ | |||||
| class NativeScaleFactorNotifier : private ComponentMovementWatcher, | |||||
| private ComponentPeer::ScaleFactorListener | |||||
| { | |||||
| public: | |||||
| /** Constructs an instance. | |||||
| While the instance is alive, it will listen for changes to the scale factor of the | |||||
| comp's peer, and will call onScaleChanged whenever this scale factor changes. | |||||
| @param comp The component to observe | |||||
| @param onScaleChanged A function that will be called when the backing scale factor changes | |||||
| */ | |||||
| NativeScaleFactorNotifier (Component* comp, std::function<void (float)> onScaleChanged); | |||||
| ~NativeScaleFactorNotifier() override; | |||||
| private: | |||||
| void nativeScaleFactorChanged (double newScaleFactor) override; | |||||
| void componentPeerChanged() override; | |||||
| using ComponentMovementWatcher::componentVisibilityChanged; | |||||
| void componentVisibilityChanged() override {} | |||||
| using ComponentMovementWatcher::componentMovedOrResized; | |||||
| void componentMovedOrResized (bool, bool) override {} | |||||
| ComponentPeer* peer = nullptr; | |||||
| std::function<void (float)> scaleChanged; | |||||
| JUCE_DECLARE_NON_COPYABLE (NativeScaleFactorNotifier) | |||||
| JUCE_DECLARE_NON_MOVEABLE (NativeScaleFactorNotifier) | |||||
| }; | |||||
| } // namespace juce | |||||
| @@ -465,17 +465,18 @@ static bool isPerMonitorDPIAwareWindow (HWND nativeWindow) | |||||
| #endif | #endif | ||||
| } | } | ||||
| static bool isPerMonitorDPIAwareThread() | |||||
| static bool isPerMonitorDPIAwareThread (GetThreadDPIAwarenessContextFunc getThreadDPIAwarenessContextIn = getThreadDPIAwarenessContext, | |||||
| GetAwarenessFromDpiAwarenessContextFunc getAwarenessFromDPIAwarenessContextIn = getAwarenessFromDPIAwarenessContext) | |||||
| { | { | ||||
| #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE | #if ! JUCE_WIN_PER_MONITOR_DPI_AWARE | ||||
| return false; | return false; | ||||
| #else | #else | ||||
| setDPIAwareness(); | setDPIAwareness(); | ||||
| if (getThreadDPIAwarenessContext != nullptr | |||||
| && getAwarenessFromDPIAwarenessContext != nullptr) | |||||
| if (getThreadDPIAwarenessContextIn != nullptr | |||||
| && getAwarenessFromDPIAwarenessContextIn != nullptr) | |||||
| { | { | ||||
| return (getAwarenessFromDPIAwarenessContext (getThreadDPIAwarenessContext()) | |||||
| return (getAwarenessFromDPIAwarenessContextIn (getThreadDPIAwarenessContextIn()) | |||||
| == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware); | == DPI_Awareness::DPI_Awareness_Per_Monitor_Aware); | ||||
| } | } | ||||
| @@ -569,12 +570,17 @@ ScopedThreadDPIAwarenessSetter::~ScopedThreadDPIAwarenessSetter() = default; | |||||
| ScopedDPIAwarenessDisabler::ScopedDPIAwarenessDisabler() | ScopedDPIAwarenessDisabler::ScopedDPIAwarenessDisabler() | ||||
| { | { | ||||
| if (! isPerMonitorDPIAwareThread()) | |||||
| static auto localGetThreadDpiAwarenessContext = (GetThreadDPIAwarenessContextFunc) getUser32Function ("GetThreadDpiAwarenessContext"); | |||||
| static auto localGetAwarenessFromDpiAwarenessContextFunc = (GetAwarenessFromDpiAwarenessContextFunc) getUser32Function ("GetAwarenessFromDpiAwarenessContext"); | |||||
| if (! isPerMonitorDPIAwareThread (localGetThreadDpiAwarenessContext, localGetAwarenessFromDpiAwarenessContextFunc)) | |||||
| return; | return; | ||||
| if (setThreadDPIAwarenessContext != nullptr) | |||||
| static auto localSetThreadDPIAwarenessContext = (SetThreadDPIAwarenessContextFunc) getUser32Function ("SetThreadDpiAwarenessContext"); | |||||
| if (localSetThreadDPIAwarenessContext != nullptr) | |||||
| { | { | ||||
| previousContext = setThreadDPIAwarenessContext (DPI_AWARENESS_CONTEXT_UNAWARE); | |||||
| previousContext = localSetThreadDPIAwarenessContext (DPI_AWARENESS_CONTEXT_UNAWARE); | |||||
| #if JUCE_DEBUG | #if JUCE_DEBUG | ||||
| ++numActiveScopedDpiAwarenessDisablers; | ++numActiveScopedDpiAwarenessDisablers; | ||||
| @@ -586,7 +592,10 @@ ScopedDPIAwarenessDisabler::~ScopedDPIAwarenessDisabler() | |||||
| { | { | ||||
| if (previousContext != nullptr) | if (previousContext != nullptr) | ||||
| { | { | ||||
| setThreadDPIAwarenessContext ((DPI_AWARENESS_CONTEXT) previousContext); | |||||
| static auto localSetThreadDPIAwarenessContext = (SetThreadDPIAwarenessContextFunc) getUser32Function ("SetThreadDpiAwarenessContext"); | |||||
| if (localSetThreadDPIAwarenessContext != nullptr) | |||||
| localSetThreadDPIAwarenessContext ((DPI_AWARENESS_CONTEXT) previousContext); | |||||
| #if JUCE_DEBUG | #if JUCE_DEBUG | ||||
| --numActiveScopedDpiAwarenessDisablers; | --numActiveScopedDpiAwarenessDisablers; | ||||
| @@ -64,6 +64,9 @@ public: | |||||
| /** Resizes this component to fit the HWND that it contains. */ | /** Resizes this component to fit the HWND that it contains. */ | ||||
| void resizeToFit(); | void resizeToFit(); | ||||
| /** Forces the bounds of the HWND to match the bounds of this component. */ | |||||
| void updateHWNDBounds(); | |||||
| /** @internal */ | /** @internal */ | ||||
| void paint (Graphics&) override; | void paint (Graphics&) override; | ||||
| @@ -610,7 +610,7 @@ private: | |||||
| if (auto* peer = owner.getPeer()) | if (auto* peer = owner.getPeer()) | ||||
| { | { | ||||
| auto r = peer->getComponent().getLocalArea (&owner, owner.getLocalBounds()); | auto r = peer->getComponent().getLocalArea (&owner, owner.getLocalBounds()); | ||||
| return r * peer->getPlatformScaleFactor(); | |||||
| return r * peer->getPlatformScaleFactor() * peer->getComponent().getDesktopScaleFactor(); | |||||
| } | } | ||||
| return owner.getLocalBounds(); | return owner.getLocalBounds(); | ||||
| @@ -165,4 +165,10 @@ void HWNDComponent::resizeToFit() | |||||
| setBounds (pimpl->getHWNDBounds()); | setBounds (pimpl->getHWNDBounds()); | ||||
| } | } | ||||
| void HWNDComponent::updateHWNDBounds() | |||||
| { | |||||
| if (pimpl != nullptr) | |||||
| pimpl->componentMovedOrResized (true, true); | |||||
| } | |||||
| } // namespace juce | } // namespace juce | ||||