Skip to content

Conversation

amartya4256
Copy link
Contributor

@amartya4256 amartya4256 commented Aug 5, 2025

This PR contributes to precise scaling of computation of size inside gridLayout by using float precision. This fixes the issue reported in #2166

Explanation

The issue happens because of the loss of precision while calculating the size of the grid in the GridLayout. The code at first calculates the size of widget in GridData#computeSize. Since the width and height in GridData were stored as integer, the returned Point.OfFloat from Control#computeSize is not fully utilized and the residualX and residualY (the floating pointer values) are lost and only integer is used.

Later this less precision integer value is used to set the bounds of the grid, which leads to wrapping the widget with the wrong size. In case of #2166, that leads to wrapping of the text in the widget while the height of the grid is set thinking that the text doesn't need wrapping.

A simple example for loss of precision:
Let's say a pixel value 223 is set at 150%.
when it is obtained in points by GridData#computeSize, it is 223/1.5 = 148.6667 i.e. 149
When scaled up on GridLayout#layout on child.setBounds, it becomes 149 * 1.5 = 223.5 i.e. 224

Hence, this difference of 1 pixel can have the wrong wrapping effect.
While using float in the layout, the inversibility is maintained. Hence, 223/1.5 = 148.66667 and 148.6666667 * 1.5 = 223

How to test

package org.eclipse.swt.snippets;

import org.eclipse.swt.*;
import org.eclipse.swt.graphics.*;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;

public class SnippetLabelCutOff {
	public static void main(String[] args) {
		Display display = new Display();


		/* Provide different resolutions for icons to get
		 * high quality rendering wherever the OS needs
		 * large icons. For example, the ALT+TAB window
		 * on certain systems uses a larger icon.
		 */
		Shell shell = new Shell(display);
		PartView partView = new SnippetLabelCutOff.PartView(display);
		partView.postConstruct(shell);
		shell.open();



		while (!shell.isDisposed()) {
			if (!display.readAndDispatch())
				display.sleep();
		}
		partView.preDestroy();
		shell.dispose();
		display.dispose();
	}

	public static class PartView
	{
	   public Display aDisplay;

	   private Composite aMidComposite;

	   private Label aRepairLabel;

	   public Font aNormalFont;

	   public PartView(Display display) {
		aDisplay = display;
	}

	   public void postConstruct(Composite pParent)
	   {
	      FormLayout vLayout = new FormLayout();
	      vLayout.marginHeight = 5;
	      vLayout.marginWidth = 6;
	      pParent.setLayout(vLayout);

	      createResources();

	      createMid(pParent);

	      pParent.pack();
	   }

	   private void createResources()
	   {
	      aNormalFont = new Font(
	            aDisplay,
	            new FontData("Arial", 11, SWT.NORMAL));

	   }

	   private void createMid(Composite pParent)
	   {
	      // MID
	      aMidComposite = new Composite(pParent, SWT.NONE);
	      aMidComposite.setBackground(aDisplay.getSystemColor(SWT.COLOR_WHITE));
	      FormData aFormDataMid = new FormData();
	      aFormDataMid.top = new FormAttachment(0, 0);
	      aFormDataMid.bottom = new FormAttachment(100, 0);
	      aFormDataMid.left = new FormAttachment(0, 0);
	      aFormDataMid.right = new FormAttachment(100, 0);
	      aMidComposite.setLayoutData(aFormDataMid);

	      GridLayout vLayout = new GridLayout();
	      aMidComposite.setLayout(vLayout);

	      aRepairLabel = new Label(aMidComposite, SWT.WRAP);
	      aRepairLabel.setFont(aNormalFont);
	      GridData vRepairLabelGridData = new GridData();
	      vRepairLabelGridData.horizontalAlignment = GridData.BEGINNING;
	      vRepairLabelGridData.verticalAlignment = GridData.CENTER;
	      vRepairLabelGridData.grabExcessHorizontalSpace = true;
	      vRepairLabelGridData.verticalIndent = Math.round(32);
	      aRepairLabel.setLayoutData(vRepairLabelGridData);

	      aRepairLabel
	            .setText(
	                  "Rhe erv fhe eovianae tb orao estnffd rniTaa eultoolte i sssube wreco.");
	   }

	   public void preDestroy()
	   {
	      aNormalFont.dispose();
	   }
	}
}

Before:
image

After:
image

depends on #2486

Copy link
Contributor

github-actions bot commented Aug 5, 2025

Test Results

104 files   -    442  104 suites   - 442   8s ⏱️ - 37m 4s
 76 tests  -  4 355   76 ✅  -  4 338  0 💤  -  17  0 ❌ ±0 
223 runs   - 16 541  223 ✅  - 16 414  0 💤  - 127  0 ❌ ±0 

Results for commit 466f39d. ± Comparison against base commit c6a29a9.

This pull request removes 4355 tests.
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
…

♻️ This comment has been updated with latest results.

@amartya4256 amartya4256 force-pushed the amartya4256/fix_label_cutoff branch 2 times, most recently from 4e2fcda to fc7dedf Compare August 6, 2025 08:43
@amartya4256 amartya4256 changed the title Amartya4256/fix label cutoff Fix label cutoff Aug 6, 2025
@amartya4256 amartya4256 marked this pull request as ready for review August 6, 2025 11:26
@amartya4256 amartya4256 linked an issue Aug 6, 2025 that may be closed by this pull request
Copy link
Contributor

@HeikoKlare HeikoKlare left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One general question on this: wouldn't it possible or even reasonable to stick to width/height values inside GridData but just make them floats and only use Point.OfFloat for the returns of API methods?
Two reasons for this:

  1. SWT is generally lacking some Size object and, unfortunately, at many places Point is used to represent a size (even though that semantically does not make that much sense). Thus, we usually try to stick to width/height when considering sizes.
  2. It would be less error prone as now if you accidentally use, e.g., size.x instead of size.getX(), you may calculate with a wrong value.

@amartya4256
Copy link
Contributor Author

One general question on this: wouldn't it possible or even reasonable to stick to width/height values inside GridData but just make them floats and only use Point.OfFloat for the returns of API methods? Two reasons for this:

  1. SWT is generally lacking some Size object and, unfortunately, at many places Point is used to represent a size (even though that semantically does not make that much sense). Thus, we usually try to stick to width/height when considering sizes.
  2. It would be less error prone as now if you accidentally use, e.g., size.x instead of size.getX(), you may calculate with a wrong value.

I agree, I'll make the changes.

@selundqma
Copy link

Hi! Is this fix included in the M3 build released last Friday?

@merks
Copy link
Contributor

merks commented Aug 25, 2025

Given the PR isn't merged, no.

@HeikoKlare
Copy link
Contributor

The necessary change was too risky to do that late (M3) in the development cycle, so it will not go in the upcoming 2025-09 relesae. We plan to process it as early as possible for 2025-12 M1.

@amartya4256 amartya4256 force-pushed the amartya4256/fix_label_cutoff branch from fc7dedf to b0d972e Compare September 1, 2025 15:08
@amartya4256
Copy link
Contributor Author

@HeikoKlare converted the fields back to float instead of Point.OfFloat and refactored as needed. Please have a look.

Copy link
Contributor

@HeikoKlare HeikoKlare left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I am not mistaken, this change breaks the calculations inside GridLayout, as previous integer divisions and remainder calculations are simply changed to float arithmetics even though the considerations of int division remainders seems to be wrong there.

Comment on lines 336 to 337
float equalWidth = (w + spanWidth) / hSpan;
float remainder = (w + spanWidth) % hSpan;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this calculation break given that this is no integer division anymore (applies to subsequent places as well)? The equalWidth wil now contain an exact division value but remainder still contains the remainder as if doing an int division.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at the logic and I realized, it was only done to regularize the size of grid cells. But now they we have size in float, we do not need to calculate remainder and we can get rid of it. I tested the code and it seems fine to me. I'll update the code here so that you can review that.

Comment on lines +162 to +167
public static Point.OfFloat from(Point point) {
if (point instanceof Point.OfFloat pointOfFloat) {
return new Point.OfFloat(pointOfFloat.getX(), pointOfFloat.getY());
}
return new Point.OfFloat(point.x, point.y);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a duplication of the method in FloatAwareGeometryFactory, isn't it? Shouldn't that be streamlined in an OS-independent way?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With vi-eclipse/Eclipse-Platform#320 we wanted to provide such utility methods directly inside rectangle and point since, they are platform independent. FloatAwareGeometryFactory is a private class inside Win32DPIUtils. Do you think it makes sense to keep using that or maybe we should move towards Point.from and Rectangle.from.

For now I can use FloatAwareGeometryFactory and create a PR following the refactoring as suggested. What do you think?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean that we should not introduce duplicated code unnecessarily. If we introduce this method as proposed, we can remove the according method from the FloatAwareGeometryFactory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's do that separately. I would also need to adapt other places. For now I'll remove this from here and use the Factory method. And I'll refactor everything in the next PR. Sounds good?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does that work? The factory is win32-specific while this is OS-independent code.

Copy link
Contributor

@laeubi laeubi Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get what you mean.

I mean why is it important where GridLayout is located for the part about FloatAwareGeometryFactory and Win32DPIUtils? A Layout Manger shold never need to know anything about DPI scaling and alike...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please read my comment above. Everything should be in there: it does not make sense (and technically you cannot even) reference OS-specific code from an OS-agnostic class (like the layout-related classes).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HeikoKlare I read the comments but they do not explain why GridLayout now needs to use Point.OfFloat and be rewritten in using internal API now, so for me it means EVERY LayoutManger now needs to be potentially be rewritten as well (what could be code outside of SWT as well).

As LayoutMangers should already work on the "points" and not the "pixels" so something seems fundamentally wrong here!

And while I can understand that there might be some slight rounding errors, the example above shows a noticeable cut-of that can not be explained by a sub-pixel rounding error to me!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@HeikoKlare I read the comments but they do not explain why GridLayout now needs to use Point.OfFloat and be rewritten in using internal API now, so for me it means EVERY LayoutManger now needs to be potentially be rewritten as well (what could be code outside of SWT as well).

Yes, that might be the case.

As LayoutMangers should already work on the "points" and not the "pixels" so something seems fundamentally wrong here!

As we know, points are imprecise because of their limitation to integers and according rounding errors.

And while I can understand that there might be some slight rounding errors, the example above shows a noticeable cut-of that can not be explained by a sub-pixel rounding error to me!

It is such an off-by-one rounding error.

Copy link
Contributor

@laeubi laeubi Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is such an off-by-one rounding error.

It does not really look like being of by a few pixels. For me it looks like more the reported size of the label is smaller than it actually should, so the Label class needs to be fixed (or the Point/Rectangle) to not round down but round up instead so in such case we get a slightly larger size (and maybe show a tiny little extra whitespace) than one that is too small. This would then fix the issue for many other cases as well instead of trying to "fix" all callers.

@@ -179,7 +179,7 @@ static int checkStyle (int style) {
if (hHint != SWT.DEFAULT) height = hHint;
width += border * 2;
height += border * 2;
return new Point (width, height);
return new Point.OfFloat (width, height);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this necessary? The calling method seems to the wrap the point into an OfFloat (via Win32DPIUtil#pixelToPoint()) anyway, doesn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It won't if the passed object is not OfFloat. Since we want to have precision scaling here, we need to return it as OfFloat so that the scaling method uses the OfFloat scaling. We have such behavior in Win32DPIUtil#pixelToPoint() because we did not want to enable OfFloat scaling SWT wide, because we had problems in some widgets if you remember. Hence, we decided to enable is specifically for those where it is needed. Which means those methods need to return an OfFloat variant to enable themselves for precision scaling.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would enable it for SWT wide once we have figured out the issues with problematic implementations. We have separate issue for that. One of them being vi-eclipse/Eclipse-Platform#303

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, I don't get it. Control#computeSize() calls Win32DPIUtils.pixelToPoint():

public Point computeSize (int wHint, int hHint, boolean changed){
checkWidget ();
int zoom = getZoom();
wHint = (wHint != SWT.DEFAULT ? Win32DPIUtils.pointToPixel(wHint, zoom) : wHint);
hHint = (hHint != SWT.DEFAULT ? Win32DPIUtils.pointToPixel(hHint, zoom) : hHint);
return Win32DPIUtils.pixelToPoint(computeSizeInPixels(wHint, hHint, changed), zoom);
}

And that method returns a Point.OfFloat if zoom is not 100:
public static Point pixelToPoint(Point point, int zoom) {
if (zoom == 100 || point == null) return point;
Point.OfFloat fPoint = FloatAwareGeometryFactory.createFrom(point);
float scaleFactor = DPIUtil.getScalingFactor(zoom);
float scaledX = fPoint.getX() / scaleFactor;
float scaledY = fPoint.getY() / scaleFactor;
return new Point.OfFloat(scaledX, scaledY);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, you are right. The behaviour I explained earlier is only valid for Rectangle, not for Point. My bad. Yes we can just return Point.

@amartya4256 amartya4256 force-pushed the amartya4256/fix_label_cutoff branch 4 times, most recently from 2c7ad44 to 81790ee Compare September 4, 2025 10:23
Copy link
Contributor

@laeubi laeubi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Layouts have always operated on whole "points" and requiring one specific layout manger for correct function seems totally wrong to me.

So I miss the following:

  1. A general description why the issue happens at all and fractions of a point make a difference now
  2. What are the plans to potentially test (and fix) other layout mangers as well
  3. How we expect custom LayoutMangers out in the wild to adapt here if the float based API is still not public.
  4. I miss test-cases that testing the new GridLayout behaviour

From the referenced issue it more seems that a relay-outing is missing here and wrapping should instead happen.

@HeikoKlare
Copy link
Contributor

  1. A general description why the issue happens at all and fractions of a point make a difference now

I agree that the description needs to be extended to make it easier to understand for everyone (and document for ourselves) what's going on. @amartya4256 please do so.

  1. What are the plans to potentially test (and fix) other layout mangers as well

We plan to do that. Sorry that we did not create a public issue for it so far. We have it in our backlog:

  1. How we expect custom LayoutMangers out in the wild to adapt here if the float based API is still not public.

Let's do one step after another. We will check the other layouts with the above mentioned ticket and see what problems we can expect in general. We have not experienced any issues with any layout but the GridLayout so far. And then it may be up to providing guidelines for others to adapt and potentially making the float-based API public.

  1. I miss test-cases that testing the new GridLayout behaviour

Yes, adding test cases for problematic cases definitely makes sense. @amartya4256 please do so.

From the referenced issue it more seems that a relay-outing is missing here and wrapping should instead happen.

That's not the case. If it was that easy, we would already have solved it like that.

@laeubi
Copy link
Contributor

laeubi commented Sep 4, 2025

We have not experienced any issues with any layout but the GridLayout so far

GridLayout gives bad result on Label changes anyways but that's a known issue. In any case a LayoutManger must be sure it can trust the reported size, so as mentioned above I more see this as a bug of the Label (and probably others) to report a wrong size, so I would first look at that issue.

In general a call to the size should report the size the component would need for the given constrains. This might not always be sufficient to show the component if constraints are to small, but given we pass in -1, -1 here it should never be too small!

@laeubi
Copy link
Contributor

laeubi commented Sep 4, 2025

Taking a quick look at the current implementation.

I think using Math.round() is wrong here for width/height because it will give a possible too small size for components being reported. Instead for width/height one would need to use Math.ceil (I think we can ignore negative numbers here) to never report a smaller size. or one would even need to introduce some rounding mode so the caller can decide an what it want to lean to.

For Point.OfFloat that is often used to measure size, I also thing it would be more safe to use Math.ceil for positive values and Math.floor for negative values instead of Math.round(). In any case I think there should be a small testcase that document (and tests!) the different rounding of "subpoint" values.

@amartya4256
Copy link
Contributor Author

I have updated the description. When it comes to tests, testing this whole issue in GridLayout as a whole is difficult as nothing is exposed as an API and the problem happens when the pixels values are wrong and not in point values. It all depends on if we have Point object or Point.OfFloat object. And testing the inversibility of pixel to point to pixel is already there in org.eclipse.swt.tests.win32.Win32DPIUtilTests.scaleDownscaleUpPointInvertible() and org.eclipse.swt.tests.win32.Win32DPIUtilTests.scaleDownscaleUpRectangleInvertible().

And on top, I can use the snippet on top as an example that shows how the label cuts off on master but this PR fixes it. Is that enough? @HeikoKlare

@laeubi
Copy link
Contributor

laeubi commented Sep 5, 2025

@amartya4256 Your example is not really intuitive to me.

You argue that the value without using floats is actually larger than it should (what is somehow expected) but why should setting it to a larger width should trigger wrapping here what should happen when the label is actually smaller so I suspect it is the other way round.

This is even reproducible under Linux GTK by modify the example with

aRepairLabel = new Label(aMidComposite, SWT.WRAP) {
	    	  @Override
	    	public void setBounds(int x, int y, int width, int height) {
	    		super.setBounds(x, y, width - 1, height);
	    	}

	    	  @Override
	    	protected void checkSubclass() {
	    	}
	      };

then the label wraps because I set a bound 1 pixel to small, but if I change it to +1 pixel (or even +10 pixels) it does not wrap.

@amartya4256 amartya4256 force-pushed the amartya4256/fix_label_cutoff branch from 81790ee to d7a4725 Compare September 8, 2025 13:18
@amartya4256
Copy link
Contributor Author

@amartya4256 Your example is not really intuitive to me.

@laeubi I had a look at the snippet itself and found the following difference in the behavior after comparing this branch with master:
On Master:

  • The label computes its size in pixel in GridData to be (659, 25) in Point.OfFloat
  • The pixels to point conversion is (439.33, 16.67) in Point.OfFloat but since GridData uses Point APIs, it is considered as (439, 17).
  • Later, child.setBounds is called in GridLayout.layout which sets the size Point(439, 17) in points.
  • The scaled value is (658, 25).
  • Here, the label shrinks by a pixel of width on scaling leading to wrapping of the text but the height remains the same.
  • (659, 25) -> (439, 17) -> (658, 25)

On this branch:

  • The label computes its size in pixel in GridData to be (659, 25) in Point.OfFloat
  • The pixels to point conversion is (439.33, 16.67) in Point.OfFloat.
  • Later child.setBounds is called in GridLayout.layout which sets the size Point.OfFloat(439.33, 16.67) in points.
  • The scaled value is (659, 25).
  • The label is set with the same size it had computed hence, the scaling is more precise and no faulty wrapping occurs.
  • (659, 25) -> (439.33, 16.67) -> (659, 25)

@laeubi
Copy link
Contributor

laeubi commented Sep 8, 2025

@amartya4256 that makes much more sense here, and that's why I would suggest to round up any values that represent a width/height. In general I think this can only solved consistently with something like this:

@fedejeanne fedejeanne force-pushed the amartya4256/fix_label_cutoff branch from d7a4725 to 185b8f3 Compare September 10, 2025 09:01
@amartya4256
Copy link
Contributor Author

@laeubi I would keep this local solution to fix this issue for now and we can carry on the implementation of #2488 later separately as this will be a long work redesigning the whole API which will also affect other platforms and also has a bigger scope.

This commit adds Rectangle.OfFloat.from and Point.OfFloat.from methods
and removes the Win32DPIUtils.FloatAwareGeometryFactory class to make
these methods OS-independent.
This commit contributes to enable GridLayout:layout to utilize
float values in GridData to calculate precise size on scaling using
Point.OfFloat APIs.

contributes to eclipse-platform#2166
@amartya4256 amartya4256 force-pushed the amartya4256/fix_label_cutoff branch from 185b8f3 to 466f39d Compare September 10, 2025 09:53
@laeubi
Copy link
Contributor

laeubi commented Sep 10, 2025

I would keep this local solution to fix this issue for now and we can carry on the implementation of #2488 later separately as this will be a long work redesigning the whole API which will also affect other platforms and also has a bigger scope.

I don't think this fix is sufficient and current implementation of Point/Rectangle rounding is to be blamed, so if you want a temporary fix you should came up with something in that are instead of any Layout manager.

This will not require a complete redesign as outlined in the previous comments. Otherwise we will end up with band-aiding many more locations (see the problem with Dialogs for example here

that suffers form the same problem of to small reported sized I think.

@fedejeanne
Copy link
Contributor

@laeubi let's continue the discussion of #2488 in that issue. You can maybe even propose a (draft) PR and adapt some of the usages in there?

I agree with @amartya4256 in here: #2488, though technically not wrong, is a massive undertaking and I fear that you are underestimating its ramifications. But as I said, please let's continue that discussion in there. No point in blocking this PR because of that.

As to your comments:

... so if you want a temporary fix you should came up with something in that are instead of any Layout manager.

Can you be more specific? How do you propose to solve it? Do you have some concrete (code) idea in mind?

Otherwise we will end up with band-aiding many more locations (see the problem with Dialogs for example here

* https://github.com/eclipse-platform/eclipse.platform.ui/pull/3212

that suffers form the same problem of to small reported sized I think.

I wouldn't draw any conclusions too fast, we don't know if these 2 issues are related. Do you?

@laeubi
Copy link
Contributor

laeubi commented Sep 10, 2025

@fedejeanne see here:

as described here:

the problem is that we round down in some cases so we get smaller sizes than actually computed.

And for me it actually is a blocker if we now start to add hacks everywhere in unrelated parts (Dialogs, LayoutMangers, ...) to address flaws in the underlying implementation especially as it will affect a whole other things we are not controlling and it seems not easy to add any tests.

So even if we can fix this single case, the same issue will arise everywhere else (maybe more visible maybe less) so soon we will add more and more "temporary fixes" that soon some code will depend on and then we can never change it again.

@fedejeanne
Copy link
Contributor

@laeubi which part of this PR adds a hack? The way I see it, it adds precision handling (uses float instead of int) which helps avoid rounding errors.

I don't see how this PR somehow blocks any new API design (#2488) nor how it does it more difficult to implement in the future.

As I said before: there is no need to postpone this PR while a new API is being designed. As soon as #2488 is ready and a new API is available, one can simply start using that API wherever it fits. If anything, use this PR as an inspiration of how the new API should work, but don't block it until the API is ready, both tasks can run without interfering each other.

@laeubi
Copy link
Contributor

laeubi commented Sep 11, 2025

@laeubi which part of this PR adds a hack?

  1. A LayoutManger should not need to be aware of (internal / provisional) API because controls report wrong size.
  2. This will only fix it for one LayoutManger (the GridLayout) and for one specific case (a label wrapping because it is a bit too small) and it hides the general problem that's all controls possibly reporting to small sizes
  3. This PR simply mix different concerns (wrong reported size) that is to be fixed in a different place (layout manager)

I don't see how this PR somehow blocks any new API design (#2488) nor how it does it more difficult to implement in the future.

It of course does not blocks this and I never said this must be the solution. It simply fixes the wrong thing at the wrong place. A LayoutManger must be able to trust to get a correct size (here it is too small), adjusting the LayoutManger because of wrong reported size is simply wrong. So if label says I do not wrap at size X but it does, its not the fault of the layout manager but wrong rounding of values in the label!

So if we now change this to "fix" then next we need to "fix" others and then the whole claim "existing code should not need to bother" is simply not true anymore.

As I said before: there is no need to postpone this PR while a new API is being designed. As soon as #2488 is ready and a new API is available, one can simply start using that API wherever it fits. If anything, use this PR as an inspiration of how the new API should work, but don't block it until the API is ready, both tasks can run without interfering each other.

So again

  • I do not propose to implement Create a new more semantic API to measure Controls and set their bounds #2488 but it would help if one want more precises layouts (what I think is not a real problem right now)
  • I proposed to fix the FloatOf / Label / whatever to rounds towards a possibly larger value for width/height instead, as the only effect would be that a control would be a few pixels larger than it could be (what we never aimed to in SWT to have the most compact layout)

@amartya4256
Copy link
Contributor Author

  1. A LayoutManger should not need to be aware of (internal / provisional) API because controls report wrong size.

The control doesn't report the wrong size. The control always returns a Point.OfFloat (All of them), but the GridLayout:layout is implemented in such a way that it ignores the floating pointer values. I don't see any issue here. The layout manager should be able to precisely layout the controls. And to do so it needs to be enabled with precision scaling otherwise there would always be a possibility of errorneous scaling. I do not agree with the point here that the Control reports the wrong size.

  1. This will only fix it for one LayoutManger (the GridLayout) and for one specific case (a label wrapping because it is a bit too small) and it hides the general problem that's all controls possibly reporting to small sizes

Of course it fixes it only for GridLayout since GridLayout in my opinion is not enabled to handle precise scaling and so it goes for all the LayoutManagers. And that's why @HeikoKlare mentioned that we have an internal issue where we intend to investigate all of them incrementally and fix them. Refer vi-eclipse/Eclipse-Platform#391

  1. This PR simply mix different concerns (wrong reported size) that is to be fixed in a different place (layout manager)

This is a wrong assumption. The sizes of controls are always scaled down using OfFloat scaling methods which leads to a precision down scaling to point values. The consumers of these must be adapted to utilize the floating residual values for a better result. Hence, the GridLayout.

It of course does not blocks this and I never said this must be the solution. It simply fixes the wrong thing at the wrong place. A LayoutManger must be able to trust to get a correct size (here it is too small), adjusting the LayoutManger because of wrong reported size is simply wrong. So if label says I do not wrap at size X but it does, its not the fault of the layout manager but wrong rounding of values in the label!

Exactly, the correct size. It obtains the Sizes in Point.OfFloat but never uses the floating values. Hence, it should be adjusted to fully utilize the values it obtains from the Control. i.e. "trusting the control".

@laeubi
Copy link
Contributor

laeubi commented Sep 12, 2025

The control doesn't report the wrong size. The control always returns a Point.OfFloat (All of them), but the GridLayout:layout is implemented in such a way that it ignores the floating pointer values.

And this assumption is simply wrong. If we take a step back and forget about LayoutMangers (what are not a requirement in SWT!) then the following code must work to ensure we retain compatibility with the existing API:

Label label = new Label(shell, SWT.WRAP);
label.setText("Some long text that would wrap");
Point maxSizeWithoutWrapping = label.computeSize(-1, -1);
//If point is rounded down this will lead to the label being too small!
label.setBounds(0,0, maxSizeWithoutWrapping.x, maxSizeWithoutWrapping.y);
// this must work as well!
// label.setBounds(new Rectangle(0, 0, maxSizeWithoutWrapping.x, maxSizeWithoutWrapping.y));

So this code will still fail with your change when the reported with is rounded down.

Given that user has no set way to use the returned Point without as-is in this operation I don't see how your assertion can hold true without violate what @HeikoKlare said here:

Adopting enhanced HiDPI support should not become a burden for adopters.

and here it obviously does. Alone in platform we have 355 reference to computeSize all the places must be adapted to a (not yet finished internal API) we also have a similar count for setBounds with a rectangle and 455 for the variant with plain integer values.

This is a wrong assumption. The sizes of controls are always scaled down using OfFloat scaling methods which leads to a precision down scaling to point values.

Just because it was always wrong does not mean it was right see below...

Exactly, the correct size. It obtains the Sizes in Point.OfFloat but never uses the floating values. Hence, it should be adjusted to fully utilize the values it obtains from the Control. i.e. "trusting the control".

There is a difference between exact and correct. So in terms of layouting (using the integer API) it might not be the absolute smallest possible size but we never claim this anywhere, the size must just be sufficiently large to paint the full control. And this is not only important for layout managers but allow when printing controls or creating images from them (where float obviously is not applicable at all)

So here just quote the javadoc

/**
* Returns the preferred size (in points) of the receiver.
* <p>
* The <em>preferred size</em> of a control is the size that it would
* best be displayed at. The width hint and height hint arguments
* allow the caller to ask a control questions such as "Given a particular
* width, how high does the control need to be to show all of the contents?"
* To indicate that the caller does not wish to constrain a particular
* dimension, the constant <code>SWT.DEFAULT</code> is passed for the hint.
* </p>
*
* @param wHint the width hint (can be <code>SWT.DEFAULT</code>)
* @param hHint the height hint (can be <code>SWT.DEFAULT</code>)
* @return the preferred size of the control
*
* @exception SWTException <ul>
* <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
* <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
* </ul>
*
* @see Layout
* @see #getBorderWidth
* @see #getBounds
* @see #getSize
* @see #pack(boolean)
* @see "computeTrim, getClientArea for controls that implement them"
*/

the important part is

The width hint and height hint arguments allow the caller to ask a control questions such as "Given a particular width, how high does the control need to be to show all of the contents?" To indicate that the caller does not wish to constrain a particular dimension, the constant SWT.DEFAULT is passed for the hint.

So if you think the width is correct then at least the height is incorrectly reported as it obviously needs to wrap and is cut-off here, and that what is claimed to be fixed here.

And that's why @HeikoKlare mentioned that we have an internal issue where we intend to investigate all of them incrementally and fix them. Refer vi-eclipse/Eclipse-Platform#391

That's by the way a litte bit hard for other contributor to follow I don't understand why such discussion agreements are not made "public" in SWT itself, one often only sees the result of such discussion and then has to find out indirectly about that. I think SWT is open enough to let such discussion happen here where people see them early to react and participate.


I also wanted to reference this (maybe historic) document that still is the reference when it comes to layouting components I think and quote/highligt some important parts

In SWT, positioning and sizing does not happen automatically. Applications can decide to size and place a Composite’s children initially or in a resize listener – or they can specify a layout class to position and size the children.
The preferred size of a widget is the minimum size needed to show its content. In the case of a Composite, the preferred size is the smallest rectangle that contains all of its children.
...
Layouts are pluggable.
...
Occasionally, you may want to write your own Layout class. Perhaps your layout needs are very complex. Maybe you have the same look in many places, and you want to take advantage of code reuse. Or you want to leverage domain knowledge to create a very efficient layout class

laeubi added a commit to laeubi/eclipse.platform.swt that referenced this pull request Sep 12, 2025
When converting pixels to point there can be depending on the zoom level
be a fractional result.
Currently in all cases these result is converted into an integer using
`Math.round()` that will make values `+/-0.5` resulting in small values
to be round towards a smaller value.

While it is maybe valid for a _location_, when using points to express a
_dimension_ this is not okay as it will result in the reported (integer)
value to be to small leading to errors when the SWT API is then used
after performing additional computations maybe.

This now makes the following adjustments:

1. Introduce a rounding mode that allows different ways of rounding and
adds as a first step ROUND (the previous default) and UP (for always
round towards the largest integer)
2. Adjust the `Control` class to decide what mode is best in what
situation.

See
- eclipse-platform#2381
- eclipse-platform#2166
@HeikoKlare
Copy link
Contributor

With the example and argumentation in #2381 (comment), I agree with @laeubi that I would actually expect Label#computeSize() to return a value that is rounded up instead of down. As a consequence, I would also expect the same issue with the snippet he provided.
However, I cannot reproduce the issue with that code. I just embedded it into a snippet that I started with swt.autoScale=quarter on different fractional zooms (including 125, 150, 175):

public static void main(String[] args) {
   System.setProperty("swt.autoScale", "quarter");
   Display display = new Display();
   Shell shell = new Shell(display);
   Label label = new Label(shell, SWT.WRAP);
   label.setText("Rhe erv fhe eovianae tb orao estnffd rniTaa eultoolte i sssube wreco.");
   Point maxSizeWithoutWrapping = label.computeSize(-1, -1);
   label.setBounds(0, 0, maxSizeWithoutWrapping.x, maxSizeWithoutWrapping.y);
   System.out.println(maxSizeWithoutWrapping);
   display.dispose();
}

Maybe I miss something? In particular, the size of the label is different from the values in #2381 (comment), so was a different text used for that? @amartya4256 can you please test/elaborate on this?

@laeubi
Copy link
Contributor

laeubi commented Sep 12, 2025

I have created now a POC just to show the general idea:

Maybe I miss something? In particular, the size of the label is different from the values in #2381 (comment), so was a different text used for that?

The problem is that you need a very specific text and a very specific font and a very specific zoom level(s) for this to work, because of this I "faked" the values a bit like in my previous example. That also makes it a bit hard to write a test for that...

@amartya4256
Copy link
Contributor Author

Maybe I miss something? In particular, the size of the label is different from the values in #2381 (comment), so was a different text used for that? @amartya4256 can you please test/elaborate on this?

I also tested your snippet. You are right, the values are different. My values were based on the snippet I have provided in the description. I took it from the issue: #2166

The Snippet uses a different font.

Try this:

public static void main(String[] args) {
		   System.setProperty("swt.autoScale", "quarter");
		   DPIUtil.setDeviceZoom(150);
		   Display display = new Display();
		   Shell shell = new Shell(display);
		   Font aNormalFont = new Font(
		            display,
		            new FontData("Arial", 11, SWT.NORMAL));
		   Label label = new Label(shell, SWT.WRAP);
		   label.setFont(aNormalFont);
		   label.setText("Rhe erv fhe eovianae tb orao estnffd rniTaa eultoolte i sssube wreco.");
		   Point maxSizeWithoutWrapping = label.computeSize(-1, -1);
		   label.setBounds(0, 0, maxSizeWithoutWrapping.x, maxSizeWithoutWrapping.y);
		   System.out.println(maxSizeWithoutWrapping);
		   display.dispose();
		}

Gives the same result.

Did I understand your your concern wrong?

@HeikoKlare
Copy link
Contributor

Did I understand your your concern wrong?

The concern is: if Label#computeSize() returns a value that is too small, (why) will it lead to cropped text if we use that size to set it as the bounds of that same label (that's what the snippet is for)? I cannot reproduce the issue with those snippets, but I do not understand why. With your example in #2381 (comment), I would expect this to fail and then the fix would need to be in Label instead of the layout, just like @laeubi proposes with #2502

@laeubi
Copy link
Contributor

laeubi commented Sep 12, 2025

if Label#computeSize() returns a value that is too small, (why) will it lead to cropped text if we use that size to set it as the bounds of that same label (that's what the snippet is for)

For a better understanding why one might want to use the snippet with this simplification:

         aRepairLabel = new Label(aMidComposite, SWT.WRAP) {
	    	  @Override
	    	public void setBounds(int x, int y, int width, int height) {
	    		super.setBounds(x, y, width - 1, height);
	    	}

	    	  @Override
	    	protected void checkSubclass() {
	    	}
	      };

This simulates that a label (with wrap enabled) is getting sets its bound with one pixel less of the width than it should. It is important to note that the height is not changed.

Now to "why it is cropped" .. in fact the label is not really cropped, but because the width is to small and the height retains as is, it leads to the label text being wrapped, so one would see the text if the height would be larger but we have given it not more space, so it looks like the text was cropped from the end (while actually it is only wrapped and the second line of text is invisible due to the height is computed for one line) when the actual painting happens.

So the LayoutManger in this case gets told a width of of X (in integer units) will paint the Label at height Y (in integer units) and finds that it has enough space for this (let it be W > X) and as we have not enabled FILL mode here it sets the size of the control to that value in the faith that then everything is fine.

Please note that the height also might not be exactly what one wants, but this is rarely noticeable as it has no influence on the wrap behavior and only characters with a large descent (eq Q or y) will slightly be cut of depending on the font style of course.

I would expect this to fail and then the fix would need to be in Label instead of the layout

Just to make clear, I think Label is not specifically affected but any Control, it is just not that prominent in other controls if the do not wrap, but for example ScrolledComposite might show scrollbars if not required or Multi-line Text inputs, Combo might get only shrink a little bit...

@amartya4256
Copy link
Contributor Author

amartya4256 commented Sep 12, 2025

@laeubi I see. While adapting Label, we need to keep in mind that we need to do this fix for all the controls which implement computeSize then. Additionally, I would propose to hide the Rounding technique inside Point.OfFloat and not expose it in the constructor since it adds to the complexity for the consumers using it. Maybe we can use a different approach to internally determine this or by using some different strategy. In this case, if we go with you approach should I close this PR or let it stay open for the discussion? @HeikoKlare

@laeubi
Copy link
Contributor

laeubi commented Sep 12, 2025

As everything goes to the DPI Util anyways and point of float is not public.... of course one can have a PointOfFLoat.asLocation() and PointOfFLoat.asDimension() ... or two different types. I just used a very minimal change to show the general idea in code to make it easier to discuss.

@amartya4256 amartya4256 marked this pull request as draft September 15, 2025 10:58
@amartya4256
Copy link
Contributor Author

@laeubi Let's go with your approach and discuss over your PR on how we can actually implement it. I have converted this PR to draft for now and with your PR merged, we can close this.

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.

Labels are cut off by autoscaling
7 participants