diff --git a/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/widgets/ControlWin32Tests.java b/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/widgets/ControlWin32Tests.java index 66f0b3c400e..e7a8547ae82 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/widgets/ControlWin32Tests.java +++ b/bundles/org.eclipse.swt/Eclipse SWT Tests/win32/org/eclipse/swt/widgets/ControlWin32Tests.java @@ -103,11 +103,11 @@ public void testCorrectScaleUpUsingDifferentSetBoundsMethod() { button.setBounds(new Rectangle(0, 47, 200, 47)); assertEquals("Control::setBounds(Rectangle) doesn't scale up correctly", - new Rectangle(0, 82, 350, 83), button.getBoundsInPixels()); + new Rectangle(0, 82, 350, 82), button.getBoundsInPixels()); button.setBounds(0, 47, 200, 47); assertEquals("Control::setBounds(int, int, int, int) doesn't scale up correctly", - new Rectangle(0, 82, 350, 83), button.getBoundsInPixels()); + new Rectangle(0, 82, 350, 82), button.getBoundsInPixels()); } @ParameterizedTest @@ -155,4 +155,123 @@ private FontComparison updateFont(int scalingFactor) { return new FontComparison(heightInPixels, currentHeightInPixels); } + /** + * Scenario: + *
+ * Layouts use client area of composites to calculate the sizes of the contained + * controls. The rounded values of that client area can lead to child bounds be + * calculated larger than the actual available size. + */ + @Test + void testChildFillsScrollableWithBadlyRoundedClientArea() { + Win32DPIUtils.setMonitorSpecificScaling(true); + Display display = Display.getDefault(); + Shell shell = new Shell(display); + Composite parent = new Composite(shell, SWT.H_SCROLL|SWT.V_SCROLL); + DPITestUtil.changeDPIZoom(shell, 125); + // Find parent bounds such that client area is rounded to a value that, + // when converted back to pixels, is one pixel too large + Rectangle parentBounds = new Rectangle(0, 0, 4, 4); + Rectangle clientAreaInPixels; + do { + do { + parentBounds.width += 1; + parentBounds.height += 1; + parent.setBounds(parentBounds); + Rectangle clientArea = parent.getClientArea(); + clientAreaInPixels = Win32DPIUtils + .pointToPixel(new Rectangle(clientArea.x, clientArea.y, clientArea.width, clientArea.height), 125); + } while (clientAreaInPixels.width <= parent.getClientAreaInPixels().width && clientAreaInPixels.width < 50); + parentBounds.x += 1; + parentBounds.y += 1; + if (parentBounds.x >= 50) { + fail("No scrolable size with non-invertible point/pixel conversion for its client area could be created"); + } + } while (clientAreaInPixels.width <= parent.getClientAreaInPixels().width); + Button child = new Button(parent, SWT.PUSH); + Rectangle childBounds = new Rectangle(0, 0, parent.getClientArea().width, parent.getClientArea().height); + child.setBounds(childBounds); + + clientAreaInPixels = parent.getClientAreaInPixels(); + Rectangle childBoundsInPixels = child.getBoundsInPixels(); + assertTrue(clientAreaInPixels.width <= childBoundsInPixels.x + childBoundsInPixels.width); + assertTrue(clientAreaInPixels.height <= childBoundsInPixels.y + childBoundsInPixels.height); + } + } diff --git a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java index 1b4c98222ea..c521382464a 100644 --- a/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java +++ b/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java @@ -3302,7 +3302,83 @@ public void setBounds (Rectangle rect) { checkWidget (); if (rect == null) error (SWT.ERROR_NULL_ARGUMENT); int zoom = computeBoundsZoom(); - setBoundsInPixels(Win32DPIUtils.pointToPixel(rect, zoom)); + Rectangle boundsInPixels = boundsToPixelsViaShellCoordinates(rect, zoom); + fitInParentBounds(boundsInPixels, zoom); + setBoundsInPixels(boundsInPixels); +} + +/** + * Converts bounds to pixels via the shell coordinate system, such that the + * coordinates for every control are rounded in the same. Otherwise, child and + * parent controls may not fit as their coordinates are relative to different + * coordinate systems (the ones with their individual parent as origin), such + * that applied rounding leads to different values for the actually same + * coordinates. One consequence when not doing this is that child controls with + * x and y set to 0 and width and height set to the parent's bounds may be + * larger than the parent. + */ +private Rectangle boundsToPixelsViaShellCoordinates(Rectangle bounds, int zoom) { + Point.OfFloat parentOffsetToShell = calculateParentOffsetToShell(); + Point.OfFloat parentOffsetToShellInPixels = Point.OfFloat + .from(Win32DPIUtils.pointToPixelAsSize(parentOffsetToShell, zoom)); + Rectangle.OfFloat offsetRectangle = new Rectangle.OfFloat(bounds.x + parentOffsetToShell.getX(), + bounds.y + parentOffsetToShell.getY(), bounds.width, bounds.height); + Rectangle.OfFloat offsetRectangleInPixels = Rectangle.OfFloat + .from(Win32DPIUtils.pointToPixel(offsetRectangle, zoom)); + int xInPixels = offsetRectangleInPixels.x - parentOffsetToShellInPixels.x; + int yInPixels = offsetRectangleInPixels.y - parentOffsetToShellInPixels.y; + int widthInPixels = offsetRectangleInPixels.width; + int heightInPixels = offsetRectangleInPixels.height; + return new Rectangle(xInPixels, yInPixels, widthInPixels, heightInPixels); +} + +private Point.OfFloat calculateParentOffsetToShell() { + float parentX = 0; + float parentY = 0; + Control parent = getParent(); + while (parent != null & !(parent instanceof Shell)) { + Rectangle.OfFloat parentLocation = Rectangle.OfFloat.from(parent.getBounds()); + parentX += parentLocation.getX(); + parentY += parentLocation.getY(); + parent = parent.getParent(); + } + return new Point.OfFloat(parentX, parentY); +} + +/** + * Cope with limited invertibility of pixel/point conversions. + *
+ * Example: 125% monitor, layout fills composite with single child + *