Skip to content

Conversation

@HeikoKlare
Copy link
Contributor

The invertibility of point/pixel conversions is limited as point values are int-based and with lower resolution than pixel values. In consequence, values need to be rounded when converted between the two, which inevitably leads to rounded values that do not fit for every use case. This adds test cases that demonstrate such use cases, including simple parent/child scenarios, in which the child is supposed to fill the parent, and including layouting scenarios incorporating the client area of a composite, and how the current implementation is not capable of producing proper results for them.

This change also adapts the methods for setting bounds/size of controls to deal with the limited invertibility:

  1. They transform the passed bounds into a "global" coordinate system (the one of the containing shell) such that all values are rounded in the same way. Otherwise, every control uses a coordinate system relative to its parent, leading to the same global point coordinates being transformed to different pixel coordinates, such that parent and child may not fit to each other even though their point coordinates perfectly match.
  2. They shrink the calculated pixel values by at most the maximum error that can be made when transforming from point to pixel values, such that rounding errors due to layouts that calculated control bounds based on a composites client area are evened out. Without that, layouted controls may be up to one point too large to fit into the composite.

Examples

The different issues are demonstrated with the following snippet (conforms to the test cases):

public static void main(String[] args) {
	System.setProperty("swt.autoScale.updateOnRuntime", "true");
	Display display = new Display();
	Shell shell = new Shell(display);
	shell.setSize(200, 200);
	int width = 11;
	int height = 11;

	Composite c1 = new Composite(shell, SWT.NONE);
	c1.addPaintListener(e -> {
		e.gc.setBackground(display.getSystemColor(SWT.COLOR_BLUE));
		e.gc.fillRectangle(0, 0, c1.getSize().x + 5, c1.getSize().x + 5);
	});
	c1.setBounds(11, 9, width, height);
	Label l1 = new Label(c1, SWT.BORDER);
	l1.setBounds(0, 0, width, height);

	Composite c2 = new Composite(shell, SWT.NONE);
	c2.addPaintListener(e -> {
		e.gc.setBackground(display.getSystemColor(SWT.COLOR_BLUE));
		e.gc.fillRectangle(0, 0, c2.getSize().x + 5, c2.getSize().x + 5);
	});
	c2.setBounds(10, 30, width, height);
	Label l2 = new Label(c2, SWT.BORDER);
	l2.setBounds(0, 0, width, height);

	width += 2;
	height += 2;
	Composite c3 = new Composite(shell, SWT.NONE);
	c3.addPaintListener(e -> {
		e.gc.setBackground(display.getSystemColor(SWT.COLOR_BLUE));
		e.gc.fillRectangle(0, 0, c3.getSize().x + 5, c3.getSize().y + 5);
	});
	c3.setBounds(13, 53, width, height);
	Label l3 = new Label(c3, SWT.BORDER);
	l3.setBounds(0, 0, width, height);

	Composite c4 = new Composite(shell, SWT.NONE);
	c4.addPaintListener(e -> {
		e.gc.setBackground(display.getSystemColor(SWT.COLOR_BLUE));
		e.gc.fillRectangle(0, 0, c4.getSize().x + 5, c4.getSize().y + 5);
	});
	GridLayout layout = new GridLayout(2, true);
	layout.marginBottom = 0;
	layout.marginTop = 0;
	layout.marginLeft = 0;
	layout.marginRight = 0;
	layout.marginHeight= 0;
	layout.marginWidth= 0;
	layout.horizontalSpacing = 0;
	c4.setLayout(layout);
	c4.setBounds(13, 100, 101, 50);
	System.out.println(c4.getBounds());
	System.out.println(c4.getClientArea());
	Label l4a = new Label(c4, SWT.BORDER);
	l4a.setBackground(display.getSystemColor(SWT.COLOR_GREEN));
	l4a.setLayoutData(new GridData(GridData.FILL_BOTH));
	Label l4b = new Label(c4, SWT.BORDER);
	l4b.setBackground(display.getSystemColor(SWT.COLOR_GREEN));
	l4b.setLayoutData(new GridData(GridData.FILL_BOTH));
	c4.layout();

	shell.open();
	while (!shell.isDisposed()) {
		if (!display.readAndDispatch())
			display.sleep();
	}

	display.dispose();
}

Before at 125%:
image

After at 125%:
image

You can easily see the behavior in layouted controls (particularly using grid layouts), such as controls based on Forms UI:

Before at 125%:
bounds_current

After at 125%:
bounds_new

The invertibility of point/pixel conversions is limited as point values
are int-based and with lower resolution than pixel values. In
consequence, values need to be rounded when converted between the two,
which inevitably leads to rounded values that do not fit for every use
case. This adds test cases that demonstrate such use cases, including
simple parent/child scenarios, in which the child is supposed to fill
the parent, and including layouting scenarios incorporating the client
area of a composite, and how the current implementation is not capable
of producing proper results for them.

This change also adapts the methods for setting bounds/size of controls
to deal with the limited invertibility:
1. They transform the passed bounds into a "global" coordinate system
(the one of the containing shell) such that all values are rounded in
the same way. Otherwise, every control uses a coordinate system relative
to its parent, leading to the same global point coordinates being
transformed to different pixel coordinates, such that parent and child
may not fit to each other even though their point coordinates perfectly
match.
2. They shrink the calculated pixel values by at most the maximum error
that can be made when transforming from point to pixel values, such that
rounding errors due to layouts that calculated control bounds based on a
composites client area are evened out. Without that, layouted controls
may be up to one point too large to fit into the composite.
@HeikoKlare HeikoKlare force-pushed the control-setbounds-shellcoordinates-light branch from 55f83e3 to 3924200 Compare November 14, 2025 14:46
@github-actions
Copy link
Contributor

Test Results

  115 files   -  3    115 suites   - 3   11m 11s ⏱️ - 7m 0s
4 618 tests  - 34  4 601 ✅  - 32  17 💤  - 2  0 ❌ ±0 
  323 runs   - 15    320 ✅  - 14   3 💤  - 1  0 ❌ ±0 

Results for commit 3924200. ± Comparison against base commit 0b21ff2.

This pull request removes 37 and adds 3 tests. Note that renamed tests count towards both.
AllGTKTests Test_GtkConverter ‑ test_HeuristicASCII_dollarSign
AllGTKTests Test_GtkConverter ‑ test_HeuristicASCII_emptyString
AllGTKTests Test_GtkConverter ‑ test_HeuristicASCII_letterA
AllGTKTests Test_GtkConverter ‑ test_HeuristicASCII_letters
AllGTKTests Test_GtkConverter ‑ test_HeuristicUTF16LE_null
AllGTKTests Test_GtkConverter ‑ test_HeuristicUTF16_AsciiLetters
AllGTKTests Test_GtkConverter ‑ test_HeuristicUTF16_Asciiletter
AllGTKTests Test_GtkConverter ‑ test_HeuristicUTF16_LotsOfLetters
AllGTKTests Test_GtkConverter ‑ test_HeuristicUTF16_letter
AllGTKTests Test_GtkConverter ‑ test_HeuristicUTF16_letters
…
org.eclipse.swt.widgets.ControlWin32Tests ‑ testChildFillsCompositeWithOffset
org.eclipse.swt.widgets.ControlWin32Tests ‑ testChildFillsScrollableWithBadlyRoundedClientArea
org.eclipse.swt.widgets.ControlWin32Tests ‑ testChildWithOffsetFillsComposite
This pull request skips 1 and un-skips 1 tests.
org.eclipse.swt.tests.junit.Test_org_eclipse_swt_widgets_Shell ‑ test_activateEventSend
org.eclipse.swt.tests.junit.Test_org_eclipse_swt_browser_Browser ‑ test_setUrl_remote_with_post

@amartya4256
Copy link
Contributor

amartya4256 commented Nov 21, 2025

I tested this branch and I found out that it reintroduces the flickering CTabFolder border line issue. I tested at 150 and 250% and was able to reproduce the flickering behaviour on resizing the CTabFolder by dragging the sides in the Sashlayout.

20251121-1407-02 2044630

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improve Control#setBounds to deal with rounding errors

2 participants