1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-01-10 23:44:24 +00:00

Plugin Client: Change scaling mechanism on Linux/Windows plugins

This commit is contained in:
reuk 2025-09-23 21:37:11 +01:00
parent b4c28db765
commit fcf1971122
No known key found for this signature in database
4 changed files with 361 additions and 340 deletions

View file

@ -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<float> 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 = &comp;
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<float> getHostScale() const
{
return scale.isHostScale() ? std::optional (scale.get()) : std::nullopt;
}
Rectangle<int> convertToHostBounds (Rectangle<float> pluginRect) const
{
jassert (observed != nullptr);
return (observed->localAreaToGlobal (pluginRect) * getPlatformAndDesktopScale()).withZeroOrigin().toNearestIntEdges();
}
Rectangle<float> convertFromHostBounds (Rectangle<int> 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<PluginScaleFactorManagerListener> listeners;
Component::SafePointer<Component> observed;
StoredScaleFactor scale;
};
} // namespace juce::detail

View file

@ -49,6 +49,7 @@
#include <juce_audio_plugin_client/juce_audio_plugin_client.h>
#include <juce_audio_plugin_client/detail/juce_CheckSettingMacros.h>
#include <juce_audio_plugin_client/detail/juce_PluginUtilities.h>
#include <juce_audio_plugin_client/detail/juce_PluginScaleFactorUtilities.h>
#include <juce_audio_plugin_client/detail/juce_LinuxMessageThread.h>
#include <juce_audio_processors_headless/utilities/juce_FlagCache.h>
@ -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<detail::MessageThread> messageThread;
#endif
@ -1509,7 +1514,7 @@ LV2_SYMBOL_EXPORT const LV2_Descriptor* lv2_descriptor (uint32_t index)
return &descriptor;
}
static Optional<float> findScaleFactor (const LV2_URID_Map* symap, const LV2_Options_Option* options)
static std::optional<float> findScaleFactor (const LV2_URID_Map* symap, const LV2_Options_Option* options)
{
if (options == nullptr || symap == nullptr)
return {};
@ -1521,7 +1526,7 @@ static Optional<float> 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<float> scaleFactorIn)
std::optional<float> 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<bool> 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<bool> 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<const float*> (opt->value);
updateScale();
plugin->getScaleManager().setHostScale (*static_cast<const float*> (opt->value));
}
return LV2_OPTIONS_SUCCESS;
}
private:
void updateScale()
{
editor->setScaleFactor (getScaleFactor());
requestResize();
}
Rectangle<int> 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<float> scaleFactor;
std::unique_ptr<AudioProcessorEditor> 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<const LV2UI_Resize*> (features, LV2_UI__resize);
const auto* symap = findMatchingFeatureData<const LV2_URID_Map*> (features, LV2_URID__map);
const auto* symap = findMatchingFeatureData<const LV2_URID_Map*> (features, LV2_URID__map);
const auto scaleFactor = findScaleFactor (symap, findMatchingFeatureData<const LV2_Options_Option*> (features, LV2_OPTIONS__options));
return new LV2UIInstance { pluginUri,

View file

@ -111,6 +111,7 @@ JUCE_END_IGNORE_WARNINGS_GCC_LIKE
#define JUCE_GUI_BASICS_INCLUDE_XHEADERS 1
#include <juce_audio_plugin_client/detail/juce_PluginUtilities.h>
#include <juce_audio_plugin_client/detail/juce_PluginScaleFactorUtilities.h>
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<AudioProcessorEditor*> (getChildComponent (0));
}
void resized() override
{
if (auto* pluginEditor = getEditorComp())
{
if (! resizingParent)
{
auto newBounds = getLocalBounds();
{
const ScopedValueSetter<bool> 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<int> r)
{
return { (int16) r.getY(), (int16) r.getX(), (int16) r.getBottom(), (int16) r.getRight() };
}
void resizeHostWindow (juce::Rectangle<int> 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<bool> 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<detail::HostDrivenEventLoop> 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<EditorCompWrapper> editorComp;
Vst2::ERect editorRect;
MidiBuffer midiEvents;

View file

@ -57,6 +57,7 @@ JUCE_BEGIN_NO_SANITIZE ("vptr")
#include <juce_audio_plugin_client/detail/juce_CheckSettingMacros.h>
#include <juce_audio_plugin_client/detail/juce_IncludeSystemHeaders.h>
#include <juce_audio_plugin_client/detail/juce_PluginUtilities.h>
#include <juce_audio_plugin_client/detail/juce_PluginScaleFactorUtilities.h>
#include <juce_audio_plugin_client/detail/juce_LinuxMessageThread.h>
#include <juce_audio_plugin_client/detail/juce_VSTWindowUtilities.h>
#include <juce_gui_basics/native/juce_WindowsHooks_windows.h>
@ -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<float> (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<HWND> (systemWindow));
const auto hostWindowScale = (decltype (factor)) detail::PluginScaleFactorManager::getScaleFactorForWindow (static_cast<HWND> (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<float> r)
static ViewRect createViewRect (Rectangle<int> 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<float> createRectangle (ViewRect viewRect)
static Rectangle<int> createRectangle (ViewRect viewRect)
{
return Rectangle<float>::leftTopRightBottom ((float) viewRect.left,
(float) viewRect.top,
(float) viewRect.right,
(float) viewRect.bottom);
}
static ViewRect convertToHostBounds (Rectangle<float> pluginRect)
{
const auto desktopScale = Desktop::getInstance().getGlobalScaleFactor();
return roundToViewRect (approximatelyEqual (desktopScale, 1.0f) ? pluginRect
: pluginRect * desktopScale);
}
static Rectangle<float> convertFromHostBounds (ViewRect hostViewRect)
{
const auto desktopScale = Desktop::getInstance().getGlobalScaleFactor();
const auto hostRect = createRectangle (hostViewRect);
return approximatelyEqual (desktopScale, 1.0f) ? hostRect
: (hostRect / desktopScale);
return Rectangle<int>::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<bool> resizingParentSetter (resizingParent, true);
setBounds (lastBounds);
}
setBounds (getSizeToContainChild());
resizeHostWindow();
}
@ -2377,7 +2329,7 @@ private:
g.fillAll (Colours::black);
}
juce::Rectangle<int> getSizeToContainChild()
Rectangle<int> 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<bool> 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<bool> 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<bool> 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<HWND> (owner.systemWindow));
if (estimatedScale > 0.0)
owner.applyScaleFactor (owner.scaleFactor.withInternal (estimatedScale));
}
void timerCallback() override
{
checkHostWindowScaleFactor();
}
#endif
std::unique_ptr<AudioProcessorEditor> pluginEditor;
private:
void peerBoundsDidUpdate() override
{
resizeHostWindow();
}
JuceVST3Editor& owner;
std::unique_ptr<EditorHostContext> editorHostContext;
Rectangle<int> 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<Cubase10WindowResizeWorkaround> 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<float> 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
//==============================================================================