From 3f13cdb3149db1ccbcc008bd1d5dcd3049074d6b Mon Sep 17 00:00:00 2001 From: reuk Date: Thu, 18 Sep 2025 16:14:59 +0100 Subject: [PATCH] HWNDComponentPeer: Add setBoundsPhysical() method to set window size in physical pixels --- .../native/juce_Windowing_windows.cpp | 114 ++++++++++-------- .../windows/juce_ComponentPeer.h | 6 + 2 files changed, 67 insertions(+), 53 deletions(-) diff --git a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp index 45dfef7a15..0321f9f298 100644 --- a/modules/juce_gui_basics/native/juce_Windowing_windows.cpp +++ b/modules/juce_gui_basics/native/juce_Windowing_windows.cpp @@ -1422,20 +1422,7 @@ public: void setBounds (const Rectangle& bounds, bool isNowFullScreen) override { - // If we try to set new bounds while handling an existing position change, - // Windows may get confused about our current scale and size. - // This can happen when moving a window between displays, because the mouse-move - // generator in handlePositionChanged can cause the window to move again. - if (inHandlePositionChanged) - return; - - if (isNowFullScreen != isFullScreen()) - setFullScreen (isNowFullScreen); - - const ScopedValueSetter scope (shouldIgnoreModalDismiss, true); - - const auto borderSize = findPhysicalBorderSize().value_or (BorderSize{}); - auto newBounds = borderSize.addedTo ([&] + setBoundsPhysical (std::invoke ([&] { ScopedThreadDPIAwarenessSetter setter { hwnd }; @@ -1445,47 +1432,12 @@ public: if (inDpiChange) return convertLogicalScreenRectangleToPhysical (bounds, hwnd); + if (GetParent (hwnd) != nullptr) + return (bounds.toDouble() * getPlatformScaleFactor()).toNearestInt(); + return convertLogicalScreenRectangleToPhysical (bounds, hwnd) .withPosition (Desktop::getInstance().getDisplays().logicalToPhysical (bounds.getTopLeft())); - }()); - - if (getTransparencyKind() == TransparencyKind::perPixel) - { - if (auto parentHwnd = GetParent (hwnd)) - { - auto parentRect = convertPhysicalScreenRectangleToLogical (D2DUtilities::toRectangle (getWindowScreenRect (parentHwnd)), hwnd); - newBounds.translate (parentRect.getX(), parentRect.getY()); - } - } - - const auto oldBounds = [this] - { - ScopedThreadDPIAwarenessSetter setter { hwnd }; - RECT result; - GetWindowRect (hwnd, &result); - return D2DUtilities::toRectangle (result); - }(); - - const bool hasMoved = (oldBounds.getPosition() != bounds.getPosition()); - const bool hasResized = (oldBounds.getWidth() != bounds.getWidth() - || oldBounds.getHeight() != bounds.getHeight()); - - DWORD flags = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED; - if (! hasMoved) flags |= SWP_NOMOVE; - if (! hasResized) flags |= SWP_NOSIZE; - - SetWindowPos (hwnd, - nullptr, - newBounds.getX(), - newBounds.getY(), - newBounds.getWidth(), - newBounds.getHeight(), - flags); - - if (hasResized && isValidPeer (this)) - { - repaintNowIfTransparent(); - } + }), isNowFullScreen); } Rectangle getBounds() const override @@ -4305,6 +4257,62 @@ private: return DefWindowProc (h, message, wParam, lParam); } + void setBoundsPhysical (const Rectangle& bounds, bool isNowFullScreen) + { + // If we try to set new bounds while handling an existing position change, + // Windows may get confused about our current scale and size. + // This can happen when moving a window between displays, because the mouse-move + // generator in handlePositionChanged can cause the window to move again. + if (inHandlePositionChanged) + return; + + if (isNowFullScreen != isFullScreen()) + setFullScreen (isNowFullScreen); + + const ScopedValueSetter scope (shouldIgnoreModalDismiss, true); + + const auto borderSize = findPhysicalBorderSize().value_or (BorderSize{}); + auto newBounds = borderSize.addedTo (bounds); + + if (getTransparencyKind() == TransparencyKind::perPixel) + { + if (auto parentHwnd = GetParent (hwnd)) + { + auto parentRect = convertPhysicalScreenRectangleToLogical (D2DUtilities::toRectangle (getWindowScreenRect (parentHwnd)), hwnd); + newBounds.translate (parentRect.getX(), parentRect.getY()); + } + } + + const auto oldBounds = std::invoke ([this] + { + ScopedThreadDPIAwarenessSetter setter { hwnd }; + RECT result; + GetWindowRect (hwnd, &result); + return D2DUtilities::toRectangle (result); + }); + + const bool hasMoved = (oldBounds.getPosition() != bounds.getPosition()); + const bool hasResized = (oldBounds.getWidth() != bounds.getWidth() + || oldBounds.getHeight() != bounds.getHeight()); + + DWORD flags = SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED; + if (! hasMoved) flags |= SWP_NOMOVE; + if (! hasResized) flags |= SWP_NOSIZE; + + SetWindowPos (hwnd, + nullptr, + newBounds.getX(), + newBounds.getY(), + newBounds.getWidth(), + newBounds.getHeight(), + flags); + + if (hasResized && isValidPeer (this)) + { + repaintNowIfTransparent(); + } + } + bool sendInputAttemptWhenModalMessage() { if (! component.isCurrentlyBlockedByAnotherModalComponent()) diff --git a/modules/juce_gui_basics/windows/juce_ComponentPeer.h b/modules/juce_gui_basics/windows/juce_ComponentPeer.h index b10de7f4f7..d9f0b99fd5 100644 --- a/modules/juce_gui_basics/windows/juce_ComponentPeer.h +++ b/modules/juce_gui_basics/windows/juce_ComponentPeer.h @@ -194,6 +194,12 @@ public: If the native window is contained in another window, then the coordinates are relative to the parent window's origin, not the screen origin. + In this case, the position is specified in the same coordinate space as the size. + + As an example, imagine that setBounds() is called on a contained ComponentPeer + with newBounds set to { 10, 20, 30, 40 }, where the peer's native scale factor is 1.5. + The new bounds will be converted to physical pixels, { 15, 30, 45, 60 }, meaning that + the peer will be positioned at { 15, 30 } *physical pixels* in the parent. This should result in a callback to handleMovedOrResized(). */