In Juce 6 isInterestedInFileDrag not called anymore when in FL Studio but was before

I have upgraded my plugin to JUCE 6 and noticed the following. The drag and drop behavior when within FL studio has changed (or is broken?).
FileDragAndDropTarget::isInterestedInFileDrag is not called anymore when I drag from the FL Studio browser to the plugin component. Interestingly drag and drop within the plugin works fine and also in the standlone version there does not seem to be an issue when e.g. dragging / dropping files from the Windows explorer.

Unfortuantely debugging did not bring any further insights. The feature is quite important for my plugin. This is definitely a JUCE regression as I can test old and new behavior here right in the same session. Also switching to DragAndDropTarget::isInterestedInDragSource did not help.

Can you give me a hint where to look at?

Edit: it is not only in FL studio. It’s happening in all DAWs (e.g. FLS, Ableton, Studio One) when using the vst2 wrapper and setting the Win 10 scaling to != 100% (see below). Sorry if I bother - but it is a showstopper for me.

After some more debugging I found this.
When dragging an external file to my component which is a public FileDragAndDropTarget in ComponentPeer::handleDragMove compUnderMouse finds a pointer to the wrong component. It is not the component where the mouse is currently hovering over.

bool ComponentPeer::handleDragMove (const ComponentPeer::DragInfo& info)
{
    auto* compUnderMouse = component.getComponentAt (info.position);
    auto* lastTarget = dragAndDropTargetComponent.get();
    Component* newTarget = nullptr;

    if (compUnderMouse != lastDragAndDropCompUnderMouse)
    {
        lastDragAndDropCompUnderMouse = compUnderMouse;
        newTarget = DragHelpers::findDragAndDropTarget (compUnderMouse, info, lastTarget);

info.position is x=420 y=461 and there sits a completly unrelated component which is not a FileDragAndDropTarget. So something goes wrong when translating the drag and drop coordinates to the JUCE component positions.

Some more debugging led me to juce_win32_Windowing.cpp and this
dragInfo.position = getMousePos (mousePos).roundToInt();
mousePos is x=2085, y=1793 which fits to the target components real bounds and is translated to the wrong x=420 y=461 by getMousePos().

By the way JUCE_WIN_PER_MONITOR_DPI_AWARE is off here.
But I have just recompiled with JUCE_WIN_PER_MONITOR_DPI_AWARE on and it is the same problem. So its unrelated to that.

Please help.

Ok, I found out that it depends on the Windows 10 GUI scaling settings. When setting the scaling to 100% it works fine as before. But this is not an acceptable workaround for anyone with a large screen. I usually use 175%.
win10scaling

And as mentioned before - this is the same when toggeling the JUCE_WIN_PER_MONITOR_DPI_AWARE and it was working in JUCE 5.

This workaround only works when the “fix scaling” option in the Win 10 advanced scaling settings is switched on.

@ed95 Is it possible that it is related to this?: GitHub Windows: Refactored DPI handling in the VST wrapper and hosting code
It also works different in the VST2 wrapper and the VST3 wrapper.

The following “hack” solves my problem. My observation is that the screen coordinates are not passed DPI-aware from Windows. Therefore it does not seem to be right to normalize them with DPI. Anyhow this fixes it for me. Could you please check if this is something that you could correct as I don’t know what sideffects it would cause?

Point<float> getMousePos (POINTL mousePos) const
{
    Point<float> screenPos;

   #if JUCE_WIN_PER_MONITOR_DPI_AWARE
    auto h = (HWND) peer.getNativeHandle();

    if (isPerMonitorDPIAwareWindow (h))
        screenPos = convertPhysicalScreenPointToLogical (pointFromPOINT ({ mousePos.x, mousePos.y }), h).toFloat();
    else
   #endif
        //screenPos = pointFromPOINT ({ mousePos.x, mousePos.y }).toFloat() / static_cast<float> (getGlobalDPI() / USER_DEFAULT_SCREEN_DPI);
			screenPos = pointFromPOINT ({ mousePos.x, mousePos.y }).toFloat();

    return peer.getComponent().getLocalPoint (nullptr, screenPos);
}
1 Like

Thanks for the report and for digging into this. The workaround you’ve posted is mostly the right idea, but we still need to take the global DPI into account when JUCE_WIN_PER_MONITOR_DPI_AWARE is disabled so we can get the scale of the display. f1a5f2e should fix this for DPI-aware and DPI-unaware windows - I’ve tested it in plenty of DAWs with both scaling enabled and disabled and it seems to be doing the right thing now but please let us know if you run into any more issues.

1 Like

Hello Ed,
thank you so much for the quick response and for looking into this. I hate to say it but f1a5f2e does not fix it for me. I have JUCE_WIN_PER_MONITOR_DPI_AWARE turned off and your fix does not seem to change something in that case. It would work for me if it would be

#if JUCE_WIN_PER_MONITOR_DPI_AWARE
        auto h = (HWND) peer.getNativeHandle();

        if (isPerMonitorDPIAwareWindow (h))
            screenPos = convertPhysicalScreenPointToLogical (screenPos.roundToInt(), h).toFloat();
        else
            screenPos /= static_cast<float> (getGlobalDPI() / USER_DEFAULT_SCREEN_DPI);
#endif

Instead of

#if JUCE_WIN_PER_MONITOR_DPI_AWARE
          auto h = (HWND) peer.getNativeHandle();

          if (isPerMonitorDPIAwareWindow (h))
             screenPos = convertPhysicalScreenPointToLogical (screenPos.roundToInt(), h).toFloat();
#else
          screenPos /= static_cast<float> (getGlobalDPI() / USER_DEFAULT_SCREEN_DPI);
#endif

But since you wrote that you have tested it, now I fear that I am doing something terribly wrong.

That won’t work for standalone builds unfortunately. Does changing that method to the following:

Point<float> getMousePos (POINTL mousePos) const
{
    auto screenPos = pointFromPOINT ({ mousePos.x, mousePos.y }).toFloat();

   #if JUCE_WIN_PER_MONITOR_DPI_AWARE
    auto h = (HWND) peer.getNativeHandle();

    if (isPerMonitorDPIAwareWindow (h))
        screenPos = convertPhysicalScreenPointToLogical (screenPos.roundToInt(), h).toFloat();
   #else
    if (JUCEApplication::isStandaloneApp())
        screenPos /= static_cast<float> (getGlobalDPI() / USER_DEFAULT_SCREEN_DPI);
   #endif

    return peer.getComponent().getLocalPoint (nullptr, screenPos);
}

work for you?

1 Like

Hi Ed. What’s happening re fixes like this making it into Juce 5 code?

1 Like

Great job! :+1: Thank you! It’s perfect.

I have tested:
Windows 10 x64 VST - Scaling 100% - Fix scaling for apps “on” - FL Studio 12 - all good!
Windows 10 x64 VST - Scaling 100% - Fix scaling for apps “on” - FL Studio 20 - all good!
Windows 10 x64 VST - Scaling 100% - Fix scaling for apps “on” - Ableton 10 - all good!
Windows 10 x64 VST - Scaling 100% - Fix scaling for apps “off” - FL Studio 12 - all good!
Windows 10 x64 VST - Scaling 100% - Fix scaling for apps “off” - FL Studio 20 - all good!
Windows 10 x64 VST - Scaling 100% - Fix scaling for apps “off” - Ableton 10 - all good!
Windows 10 x64 VST - Scaling 175% - Fix scaling for apps “on” - FL Studio 12 - all good!
Windows 10 x64 VST - Scaling 175% - Fix scaling for apps “on” - FL Studio 20 - all good!
Windows 10 x64 VST - Scaling 175% - Fix scaling for apps “on” - Ableton 10 - all good!
Windows 10 x64 VST - Scaling 175% - Fix scaling for apps “off” - FL Studio 12 - all good!
Windows 10 x64 VST - Scaling 175% - Fix scaling for apps “off” - FL Studio 20 - all good!
Windows 10 x64 VST - Scaling 175% - Fix scaling for apps “off” - Ableton 10 - all good!

Windows 10 x64 Standalone - Scaling 100% - Fix scaling for apps “on” - all good!
Windows 10 x64 Standalone - Scaling 100% - Fix scaling for apps “off” - all good!
Windows 10 x64 Standalone - Scaling 175% - Fix scaling for apps “on” - all good!
Windows 10 x64 Standalone - Scaling 175% - Fix scaling for apps “off” - all good!

For VST3 I was lazy - I have only tested:
Windows 10 x64 VST3 - Scaling 100% - Fix scaling for apps “on” - FL Studio 20 - all good!
Windows 10 x64 VST3 - Scaling 175% - Fix scaling for apps “on” - FL Studio 20 - all good!

1 Like

Hi Ed,
there is still one related thing that puzzles me and which has changed and causes a different behavior (and IMHO wrong scaling) between VST2 and VST3.
The VST3 wrapper receives a call (from the host?) to setContentScaleFactor() with the Windows scaling setting. So in my examples above I get a 1.0 for 100%, 1.5 or 1.75 for 150% or 175% scaling.
Yet in the VST2 wrapper this call does not happen. setContentScaleFactor() is not called. As a result my VST3 editor is scaled and the VST2 editor is not. Not sure if this a DAW thing. Shouldn’t this be the same behaviour?

Is this with JUCE_WIN_PER_MONITOR_DPI_AWARE disabled? Support for Windows DPI scaling in VST2 is patchy at best and is done via manufacturer specific extensions, therefore in the JUCE VST2 wrapper there are workarounds to check the scale factor of the HWND directly via Win32 methods. These are disabled when JUCE_WIN_PER_MONITOR_DPI_AWARE is not set. In VST3, setting the scale factor of a plug-in window is supported in the API itself and has much better DAW support, therefore it’s likely that you are seeing more DAWs supporting scaling for VST3.

Yes, it is with JUCE_WIN_PER_MONITOR_DPI_AWARE disabled. I am still struggling with the setContentScaleFactor() logic that creates an affineTransform in the AudioProcessorEditor. Is there a way to turn that off? Why am I asking: I have some scaling logic also for graphical parts of the plugin. When the plugin becomes larger I want to draw more details because there is more space available. The affineTransform prevents that since it is just scaling the smaller resolution up.

Not currently, no. Disabling JUCE_WIN_PER_MONITOR_DPI_AWARE will make JUCE programs on Windows ignore the desktop scaling factor set by the user, but it’s a little more nuanced in plug-ins as we need to scale the plug-in to the factor sent via the setContentScaleFactor() call (it might not be the same as the Windows scaling setting, it could be a DAW-specific scale factor).

Having the amount of content drawn in your window depend on the scale factor seems odd though, as a user you’d expect the window to look the same but just be scaled up - users might be scaling your window for accessibility reasons for example.

Maybe I did not articulate it precise enough. I did not mean the amount of content but the level of detail. I have a wavetable display component drawing a perspective projection of 256 cycles x 2048 samples. When the component is e.g. 200 x 100 pixels I can draw max ~ 10% of each single cycle waveform. But if it is 600 x 300 around ~30% which makes it look much nicer and detailed. Scaling up the 200 x 100 image makes it look blurry and information is lost.

Probably a perfect solution for me would be to have some sort of flag in AudioProcessorEditor that I can set which skips the affineTransform logic and instead just sets new bounds. So if the host calls with scale factor 2.0 the editor just sets new bounds with 2.0 * width, 2.0 * height. the host wants it twice as big - it gets it twice as big. My plugin has to deal with all the rest that needs to happen on resized().

How are you drawing the waveform? If you are using the normal JUCE graphics primitives then the vector graphics will scale without losing any information or becoming blurry when scaled.

Yes, all with JUCE graphics primitives. But no vector graphics. I am simply drawing using juce::Graphics.
Here is an “extreme” example screenshot on the same screen. FL studio GUI scaling set to 400% on a 3840x2160 display resolution! As mentioned VST (top graphics) is handling the scaling on its own and VST3 (bottom graphics is using the affineTransform - so it actually believes it is an ~500x300 pixel plugin editor that has to be scaled up to a screen size of ~2000x1200px).


I personally like the top one more. :wink:

Can you post some of the drawing code? If you are just using the Graphics primitives like Paths, drawRect(), fillRect() etc. then these will draw the same quality regardless of any AffineTransforms. Here’s a contrived example I built running as a VST2 in FL Studio with 350% scale:

flscaling

Thanks for all the effort you have put into this. Maybe I still haven’t understood some basic GUI scaling concepts here. In your example the plugin’s screen bounds are 3.5 x larger with the 350% scale, right? My use case is different - and maybe not common: my plugin editor is freely resizable. A user can just decide that he/she wants to use let’s say 1775x1025px. When the scale factor in the DAW is then changed I keep that size as it was deliberatley chosen by the user - so the plugin screen size remains 1775x1025px unless the user enlarges it explicitly, but now e.g. with a scale factor and transformation of 3.5. So I am talking about the drawing quality always compared for plugin editor windows with the identical screen size, but different scaling.

The wavetable display is a small component in my editor - let’s say it is 290x145px with scale 1.0 and 82x41px with scale 3.5 - given the same plugin editor screen(!) sizes for both.
Here is some stripped down code for drawing the wavetable.

 Graphics g(waveformImage);
 float rwidth = getWidth(); 
 for (int wtpos = wtposfrom; wtpos >= wtposto; wtpos--) {
  	Path myWave; 			
    for (int x = 0; x < rwidth; x++) {
      	int xWT = int((x * step) + (m_safePhaseFloat * C_WAVE_TABLE_SIZE)) % C_WAVE_TABLE_SIZE;
       	float yVal = wavetableThreadsafeCopy[wtpos][xWT];
       	float halfHeightY = toScreenCoordinate(Point<float>(xoffset, halfHeight + yoffset)).y;
       	float mpoint = halfHeightY - (halfHeight  * yVal * 0.2f);
       	mpoint = toScreenCoordinate(Point<float>(x + xoffset, mpoint)).y;
       	if (x == 0) 
            myWave.startNewSubPath(xoffset, mpoint);
       	myWave.lineTo(x + xoffset, mpoint);
    }
   	g.strokePath(myWave, PathStrokeType(lStrokeSize));
 }

For scale 1.0 I am drawing from x=0 to 289 (getWidth() - 1) and for scale 3.5 from x=0 to 81 (getWidth() - 1).
This is obviously the reason for the different drawing quality. Is this fundamentally wrong?

I think the root issue here is allowing the user to set the plug-in size in terms of pixels and trying to maintain this in your drawing - on most, if not all, modern displays and OSes 1 pixel in user space != 1 physical pixel. JUCE itself only deals in logical pixels, where 1 pixel in JUCE-land can represent several physical pixels on the screen. This means that you don’t need to deal with all the possible device scaling factors and sizes, and JUCE windows will look consistent across all devices. Trying to bypass this and draw directly to physical screen pixels is a bad idea and you’ll end up with situations like the image you posted.

If a user is setting a scale factor, either in the DAW or via the display preferences, your plug-in window size should be scaled too and you shouldn’t try to maintain a consistent size in physical pixels. As I said earlier, this could be needed for accessibility reasons and blocking this could be hindering those who are visually impaired and need a larger window size. I’d recommend enabling JUCE_WIN_PER_MONITOR_DPI_AWARE for your builds and allowing the host to scale your plug-in window, instead of trying to do it yourself.

Thanks. I will rework the whole scaling logic in my plugin to be more aligned with the JUCE scaling concepts as soon as there is time.
One additional root cause for the problem shown in my image above is probably this: I am drawing to an image buffer of the size of the component’s bounds.

waveformImage = Image(Image::RGB, getWidth(), getHeight(), false);
Graphics g(waveformImage);

Later in paint(Graphics& g) I am drawing it:

g.drawImageAt(waveformImage, 0, 0);

This seems to be a terrible idea when scaling is involved since the juce::Image is physically bound to its size and cannot deal with logical pixels.