-
Notifications
You must be signed in to change notification settings - Fork 182
Fix label cutoff #2381
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Fix label cutoff #2381
Conversation
Test Results104 files - 442 104 suites - 442 8s ⏱️ - 37m 4s Results for commit 466f39d. ± Comparison against base commit c6a29a9. This pull request removes 4355 tests.
♻️ This comment has been updated with latest results. |
4e2fcda
to
fc7dedf
Compare
bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/layout/GridData.java
Outdated
Show resolved
Hide resolved
bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/layout/GridData.java
Outdated
Show resolved
Hide resolved
There was a problem hiding this 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:
- SWT is generally lacking some
Size
object and, unfortunately, at many placesPoint
is used to represent a size (even though that semantically does not make that much sense). Thus, we usually try to stick towidth
/height
when considering sizes. - It would be less error prone as now if you accidentally use, e.g.,
size.x
instead ofsize.getX()
, you may calculate with a wrong value.
I agree, I'll make the changes. |
bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/layout/GridLayout.java
Outdated
Show resolved
Hide resolved
Hi! Is this fix included in the M3 build released last Friday? |
Given the PR isn't merged, no. |
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. |
fc7dedf
to
b0d972e
Compare
@HeikoKlare converted the fields back to float instead of Point.OfFloat and refactored as needed. Please have a look. |
There was a problem hiding this 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.
bundles/org.eclipse.swt/Eclipse SWT/common/org/eclipse/swt/layout/GridData.java
Outdated
Show resolved
Hide resolved
float equalWidth = (w + spanWidth) / hSpan; | ||
float remainder = (w + spanWidth) % hSpan; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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); | ||
} |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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!
There was a problem hiding this comment.
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 EVERYLayoutManger
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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()
:
eclipse.platform.swt/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java
Lines 618 to 624 in 1ae0e18
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:Lines 116 to 123 in 1ae0e18
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); | |
} |
There was a problem hiding this comment.
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
.
2c7ad44
to
81790ee
Compare
There was a problem hiding this 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:
- A general description why the issue happens at all and fractions of a point make a difference now
- What are the plans to potentially test (and fix) other layout mangers as well
- How we expect custom
LayoutMangers
out in the wild to adapt here if the float based API is still not public. - 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.
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.
We plan to do that. Sorry that we did not create a public issue for it so far. We have it in our backlog:
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
Yes, adding test cases for problematic cases definitely makes sense. @amartya4256 please do so.
That's not the case. If it was that easy, we would already have solved it like that. |
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 |
Taking a quick look at the current implementation. I think using For |
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 |
@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
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. |
81790ee
to
d7a4725
Compare
@laeubi I had a look at the snippet itself and found the following difference in the behavior after comparing this branch with master:
On this branch:
|
@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: |
d7a4725
to
185b8f3
Compare
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
185b8f3
to
466f39d
Compare
I don't think this fix is sufficient and current implementation of 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. |
@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:
Can you be more specific? How do you propose to solve it? Do you have some concrete (code) idea in mind?
I wouldn't draw any conclusions too fast, we don't know if these 2 issues are related. Do you? |
@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. |
@laeubi which part of this PR adds a hack? The way I see it, it adds precision handling (uses 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. |
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 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.
So again
|
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.
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
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.
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". |
And this assumption is simply wrong. If we take a step back and forget about
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
and here it obviously does. Alone in platform we have 355 reference to
Just because it was always wrong does not mean it was right see below...
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 eclipse.platform.swt/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Control.java Lines 553 to 579 in c6a29a9
the important part is
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.
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
|
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
With the example and argumentation in #2381 (comment), I agree with @laeubi that I would actually expect 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? |
I have created now a POC just to show the general idea:
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... |
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:
Gives the same result. Did I understand your your concern wrong? |
The concern is: if |
For a better understanding why one might want to use the snippet with this simplification:
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 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
Just to make clear, I think |
@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 |
As everything goes to the DPI Util anyways and point of float is not public.... of course one can have a |
@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. |
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
Before:

After:

depends on #2486