@@ -155,4 +155,205 @@ private FontComparison updateFont(int scalingFactor) {
155155 return new FontComparison (heightInPixels , currentHeightInPixels );
156156 }
157157
158+ /**
159+ * Scenario:
160+ * <ul>
161+ * <li>parent has bounds with an offset (x != 0) to its parent
162+ * <li>child fills the composite, such that both their widths are equal
163+ * </ul>
164+ * Depending on how the offset of the parent (x value of bounds) is taken
165+ * into account when rounding during point-to-pixel conversion, the parent
166+ * composite may become one pixel too large or small for the child.
167+ */
168+ @ Test
169+ void testChildFillsCompositeWithOffset () {
170+ Win32DPIUtils .setMonitorSpecificScaling (true );
171+ // pixel values at 125%: (2.5, 2.5, 2.5, 2.5) --> when rounding bottom right
172+ // corner (pixel value (5, 5)) instead of width/height independently, will be
173+ // rounded to (3, 3, 2, 2) --> too small for child
174+ Rectangle parentBounds = new Rectangle (2 , 2 , 2 , 2 );
175+ // pixel values at 125%: (0, 0, 2.5, 2.5) --> will be rounded to (0, 0, 3, 3)
176+ Rectangle childBounds = new Rectangle (0 , 0 , 2 , 2 );
177+
178+ Display display = Display .getDefault ();
179+ Shell shell = new Shell (display );
180+ Composite parent = new Composite (shell , SWT .NONE );
181+ DPITestUtil .changeDPIZoom (shell , 125 );
182+ parent .setBounds (parentBounds );
183+ Button child = new Button (parent , SWT .PUSH );
184+ child .setBounds (childBounds );
185+
186+ Rectangle parentBoundsInPixels = parent .getBoundsInPixels ();
187+ Rectangle childBoundsInPixels = child .getBoundsInPixels ();
188+ assertEquals (parentBoundsInPixels .x , 3 );
189+ assertEquals (childBoundsInPixels .x , 0 );
190+ assertEquals (parentBoundsInPixels .width , childBoundsInPixels .width );
191+ assertEquals (parentBoundsInPixels .height , childBoundsInPixels .height );
192+ assertEquals (childBounds , child .getBounds ());
193+ }
194+
195+ /**
196+ * Scenario:
197+ * <ul>
198+ * <li>parent has bounds with an offset (x = 0) to its parent
199+ * <li>child has an offset (x != 0) to parent and exactly fills the rest of the
200+ * composite, such that child.x+child.width is equal to parent.x
201+ * </ul>
202+ * Depending on how the offset of the child (x value of bounds) is taken into
203+ * account when rounding during point-to-pixel conversion, the child may become
204+ * one pixel too large to fit into the parent.
205+ */
206+ @ Test
207+ void testChildWithOffsetFillsComposite () {
208+ Win32DPIUtils .setMonitorSpecificScaling (true );
209+ // pixel values at 125%: (0, 0, 5, 5)
210+ Rectangle parentBounds = new Rectangle (0 , 0 , 4 , 4 );
211+ // pixel values at 125%: (2.5, 2.5, 2.5, 2.5) --> when rounding width/height
212+ // independently instead of bottom right corner, will be rounded to
213+ // (3, 3, 3, 3) --> too large for parent
214+ Rectangle childBounds = new Rectangle (2 , 2 , 2 , 2 );
215+
216+ Display display = Display .getDefault ();
217+ Shell shell = new Shell (display );
218+ Composite parent = new Composite (shell , SWT .NONE );
219+ DPITestUtil .changeDPIZoom (shell , 125 );
220+ parent .setBounds (parentBounds );
221+ Button child = new Button (parent , SWT .PUSH );
222+
223+ child .setBounds (childBounds );
224+ assertChildFitsIntoParent (parent , child );
225+ assertEquals (parentBounds , parent .getBounds ());
226+ assertEquals (childBounds , child .getBounds ());
227+
228+ child .setSize (childBounds .width , childBounds .height );
229+ assertChildFitsIntoParent (parent , child );
230+ assertEquals (parentBounds , parent .getBounds ());
231+ assertEquals (childBounds , child .getBounds ());
232+ }
233+
234+ private void assertChildFitsIntoParent (Control parent , Control child ) {
235+ Rectangle parentBoundsInPixels = parent .getBoundsInPixels ();
236+ Rectangle childBoundsInPixels = child .getBoundsInPixels ();
237+ Rectangle childBounds = child .getBounds ();
238+ assertEquals (parentBoundsInPixels .width , childBoundsInPixels .x + childBoundsInPixels .width );
239+ assertEquals (parentBoundsInPixels .height , childBoundsInPixels .y + childBoundsInPixels .height );
240+ assertEquals (parent .getBounds ().width , childBounds .x + childBounds .width );
241+ assertEquals (parent .getBounds ().height , childBounds .y + childBounds .height );
242+ }
243+
244+ /**
245+ * Scenario: Layouting
246+ * <p>
247+ * Layouts use client area of composites to calculate the sizes of the contained
248+ * controls. The rounded values of that client area can lead to child bounds be
249+ * calculated larger than the actual available size. This serves as unit test
250+ * for that behavior in addition to the integration test
251+ * {@link #testChildFillsScrollableWithBadlyRoundedClientArea()}
252+ */
253+ @ Test
254+ void testFitChildIntoParent () {
255+ Win32DPIUtils .setMonitorSpecificScaling (true );
256+ // pixel values at 125%: (0, 0, 5, 5)
257+ Rectangle parentBounds = new Rectangle (0 , 0 , 4 , 4 );
258+ // pixel values at 125%: (2.5, 2.5, 3.75, 3.75) --> rounded to (3, 3, 3, 3)
259+ Rectangle childBounds = new Rectangle (2 , 2 , 3 , 3 );
260+
261+ Display display = Display .getDefault ();
262+ Shell shell = new Shell (display );
263+ Composite parent = new Composite (shell , SWT .NONE );
264+ DPITestUtil .changeDPIZoom (shell , 125 );
265+ parent .setBounds (parentBounds );
266+ Button child = new Button (parent , SWT .PUSH );
267+
268+ child .setBounds (childBounds );
269+ assertChildFitsIntoParent (parent , child );
270+
271+ child .setSize (childBounds .width , childBounds .height );
272+ assertChildFitsIntoParent (parent , child );
273+ }
274+
275+ // Ensure that the fitting logic does only apply at off-by-one calculation results
276+ // and not for fitting actually larger childs into parts
277+ @ Test
278+ void testFitChildIntoParent_limitedSizeDifference () {
279+ Win32DPIUtils .setMonitorSpecificScaling (true );
280+ // pixel values at 125%: (0, 0, 5, 5)
281+ Rectangle parentBounds = new Rectangle (0 , 0 , 4 , 4 );
282+ // pixel values at 125%: (2.5, 2.5, 5, 5) --> rounded to (3, 3, 5, 5)
283+ Rectangle childBounds = new Rectangle (2 , 2 , 4 , 4 );
284+
285+ Display display = Display .getDefault ();
286+ Shell shell = new Shell (display );
287+ Composite parent = new Composite (shell , SWT .NONE );
288+ DPITestUtil .changeDPIZoom (shell , 125 );
289+ parent .setBounds (parentBounds );
290+ Button child = new Button (parent , SWT .PUSH );
291+
292+ child .setBounds (childBounds );
293+ assertChildNotFitsIntoParent (parent , child );
294+
295+ child .setSize (childBounds .width , childBounds .height );
296+ assertChildNotFitsIntoParent (parent , child );
297+ }
298+
299+ private void assertChildNotFitsIntoParent (Control parent , Control child ) {
300+ Rectangle parentBoundsInPixels = parent .getBoundsInPixels ();
301+ Rectangle childBoundsInPixels = child .getBoundsInPixels ();
302+ Rectangle childBounds = child .getBounds ();
303+ assertNotEquals (parentBoundsInPixels .width , childBoundsInPixels .x + childBoundsInPixels .width );
304+ assertNotEquals (parentBoundsInPixels .height , childBoundsInPixels .y + childBoundsInPixels .height );
305+ assertNotEquals (parent .getBounds ().width , childBounds .x + childBounds .width );
306+ assertNotEquals (parent .getBounds ().height , childBounds .y + childBounds .height );
307+ }
308+
309+ /**
310+ * Scenario: Layouting
311+ * <p>
312+ * Layouts use client area of composites to calculate the sizes of the contained
313+ * controls. The rounded values of that client area can lead to child bounds be
314+ * calculated larger than the actual available size.
315+ */
316+ @ Test
317+ void testChildFillsScrollableWithBadlyRoundedClientArea () {
318+ Win32DPIUtils .setMonitorSpecificScaling (true );
319+ Display display = Display .getDefault ();
320+ Shell shell = new Shell (display );
321+ Composite parent = new Composite (shell , SWT .H_SCROLL |SWT .V_SCROLL );
322+ DPITestUtil .changeDPIZoom (shell , 125 );
323+ // Find parent bounds such that client area is rounded to a value that,
324+ // when converted back to pixels, is one pixel too large
325+ Rectangle parentBounds = new Rectangle (0 , 0 , 4 , 4 );
326+ Rectangle clientAreaInPixels ;
327+ do {
328+ do {
329+ parentBounds .width += 1 ;
330+ parentBounds .height += 1 ;
331+ parent .setBounds (parentBounds );
332+ Rectangle clientArea = parent .getClientArea ();
333+ clientAreaInPixels = Win32DPIUtils
334+ .pointToPixel (new Rectangle (clientArea .x , clientArea .y , clientArea .width , clientArea .height ), 125 );
335+ } while (clientAreaInPixels .width <= parent .getClientAreaInPixels ().width && clientAreaInPixels .width < 50 );
336+ parentBounds .x += 1 ;
337+ parentBounds .y += 1 ;
338+ if (parentBounds .x >= 50 ) {
339+ fail ("No scrolable size with non-invertible point/pixel conversion for its client area could be created" );
340+ }
341+ } while (clientAreaInPixels .width <= parent .getClientAreaInPixels ().width );
342+ Button child = new Button (parent , SWT .PUSH );
343+ Rectangle childBounds = new Rectangle (0 , 0 , parent .getClientArea ().width , parent .getClientArea ().height );
344+ child .setBounds (childBounds );
345+
346+ clientAreaInPixels = parent .getClientAreaInPixels ();
347+ Rectangle childBoundsInPixels = child .getBoundsInPixels ();
348+ assertTrue (clientAreaInPixels .width <= childBoundsInPixels .x + childBoundsInPixels .width );
349+ assertTrue (clientAreaInPixels .height <= childBoundsInPixels .y + childBoundsInPixels .height );
350+
351+ child .setSize (childBounds .width , childBounds .height );
352+
353+ clientAreaInPixels = parent .getClientAreaInPixels ();
354+ childBoundsInPixels = child .getBoundsInPixels ();
355+ assertTrue (clientAreaInPixels .width <= childBoundsInPixels .x + childBoundsInPixels .width );
356+ assertTrue (clientAreaInPixels .height <= childBoundsInPixels .y + childBoundsInPixels .height );
357+ }
358+
158359}
0 commit comments