eclipse.platform.swt icon indicating copy to clipboard operation
eclipse.platform.swt copied to clipboard

Create a new more semantic API to measure Controls and set their bounds

Open laeubi opened this issue 4 months ago • 11 comments

Currently SWT is heavy dependent on very generic datatypes (e.g. primitive int or simple containers like Rectangle and Point) and we try to circumvent this limitation with some extensions like Point/Rectangle with monitors, Float Points and so on but the general limitations stay:

  1. if using a int/float as a primitive value it is easy to mix up things and these can not carry internal data easily
  2. Points and Rectangles are used for different purposes, e.g. we use points to measure a size (e.g. Strings rendered with a font, or size of a Control) but also to express a location (e.g. where a shell should be shown or a click event or similar), also here it is absolutely possible to "mix" different things.
  3. There is no way to give something as a unit of things, e.g. when specify the width of a control that shows some text, giving that in any absolute unit usually does not give good results as the font-size might vary per system, per OS or even per user account. Instead one would want to use relative units (e.g. percent, or em) what is quite common in CSS and responsive designs.

I therefore like to suggest we are adding new types and APIs to controls that account for that fact especially:

  • A Width and a Height type that can carry a value and optionally a unit and a Size that is a combination of Width and Height
  • A Position and a Bounds what would be a combination of a Position and a Size
  • ...

These types should then offer ways to perform mathematical operations on them in a type-safe fashion, e.g. one should not be able to add a Width and a Height directly and we need new API methods that accept and return such types.

With that for example a Width given in pixels can easily carry a zoom factor or monitor from its control it was gathered from and return a value in Point or ems.

As an example

public void setBounds (Bounds bound);
public Size computeSize (Width wHint, Height hHint);

Would then a layout manager allow to compute all values with type Width when it comes to compute a size like this:

Bounds availableArea = control.getBounds();
Control[] childs = control.getChildren();
Width evenWidth = availableArea.divide(childs.length);
Position current = Position.origin();
foreach(Control c : childs) {
  c.setBounds(Bounds.of(current, Size.of(evenWidth, c.computeSize(evenWidth, Height.DEFAULT).height());
  current = current.add(evenWidth);
}

such code then can even use the native pixel values internally without any precision loss. Such values then even might be dynamically scaled base on the current Monitor position instead of being recomputed all the way long.

laeubi avatar Sep 08 '25 07:09 laeubi

I guess everyone agrees that having proper types for, e.g., sizes/dimensions would be great (though in the concrete examples I don't see why "width" and "height" need to be separate types and not roles of the same type). When designing new APIs/frameworks, that should be the way to go. But keep in mind that we have a long standing framework with long standing APIs that cannot be easily modified/extended in such a way. You will not be able to remove the existing API using less-typed values after consumers have relied on it for such a long time, so you are stuck with fitting to that existing API anyway. In addition, the question is whether it's worth all the effort and who should carry it. In particular, the problems that we face with HiDPI support for fractional zoom values is not about the types of values but mostly about their precision, so there is no current motification for such kind of change.

We had a discussion going in a similar direction here:

  • https://github.com/eclipse-platform/eclipse.platform.swt/issues/2129

I still think it's great to have this issue to point to when such discussions come up in issues/PRs, so we can refer to this issue even if (or exactly because) this proposal will probably never be realized.

HeikoKlare avatar Sep 10 '25 16:09 HeikoKlare

I don't see why "width" and "height" need to be separate types and not roles of the same type). When designing new APIs/frameworks, that should be the way to go.

I think the details can be discussed of course, a simple rationale is what should be the result of adding a width and a height what would not be really useful (while multiply a width with a height can given an Area), also we have some API where we want to give a width and a height so having own types prevent accidental using of the wrong parameter.

Just assume you have setSize(Height.of(50), Width.of(25)) versus setSize(Length.of(50), Length.of(25)) the first example will give you a compile error (and is much easier to spot) while the second will not give an error and you need to know the order of argument. Anyways such things would maybe of course extend a common type.

But keep in mind that we have a long standing framework with long standing APIs that cannot be easily modified/extended in such a way. You will not be able to remove the existing API using less-typed values after consumers have relied on it for such a long time, so you are stuck with fitting to that existing API anyway.

While this is true I don't think it should limit us to make SWT better, the same arguments hold true for generics, records or maybe any API enhancement. And soon a "new" API can become an established one used at many places.

In addition, the question is whether it's worth all the effort and who should carry it. In particular, the problems that we face with HiDPI support for fractional zoom values is not about the types of values but mostly about their precision, so there is no current motification for such kind of change

I disagree here. It is not only about precision (lets ignore the fact that is uses imprecise floats versus doubles or even BigDecimals and that not all values can be exactly represented as a floatingpoint anyways) but also about mixing types.

A Point is used as a location (what always has exactly one Monitor/DPI) and as a dimension (what can have up to 4 monitors/DPI) and both would need different rounding rules e.g. while I want to round up a dimension I would maybe simply use the closes for a location.

I still think it's great to have this issue to point to when such discussions come up in issues/PRs, so we can refer to this issue even if (or exactly because) this proposal will probably never be realized.

Many things are claimed to be never realized, but I think for a real good (dynamic) HighDPI support it is simply needed, the current way seems not scaling well in terms of corner cases and given that SWT is extensible. Also we do not need everything as once and why I used setBounds / computeSize in my example, these are methods that can quite easy be made backward-compatible, contribute to an actual problem and are nothing one usually faces in a regular client code (assuming layout managers are used) and would perfectly fit into "if you want best HiDPI support use these new methods otherwise you might see glitches".

laeubi avatar Sep 11 '25 06:09 laeubi

Just assume you have setSize(Height.of(50), Width.of(25)) versus setSize(Length.of(50), Length.of(25)) the first example will give you a compile error (and is much easier to spot) while the second will not give an error and you need to know the order of argument. Anyways such things would maybe of course extend a common type.

That looks way too verbose. I can also already see the boilerplate code of when you e.g want to transpose the object and therefore need to convert the width to a height and vice versa...

For Javas Temporal API, you have the java.time.temporal.ChronoUnit enum to define the unit of time. And I think something similar would find more acceptance from users. e.g. setSize(50, 25, Unit.PIXEL), which SWT can then convert into whatever internal structure would be appropriate.

you need to know the order of argument

Doesn't every modern IDE provide some option to show the parameter names?

Image

A Point is used as a location (what always has exactly one Monitor/DPI) and as a dimension (what can have up to 4 monitors/DPI) and both would need different rounding rules e.g. while I want to round up a dimension I would maybe simply use the closes for a location.

A similar point was also brought up in #2129 and I consider this to be a genuine flaw in SWT, which e.g. Swing got right. And I still think that adding a Dimension type is worth discussing, outside of a more semantic API.

"if you want best HiDPI support use these new methods otherwise you might see glitches"

And this is the statement I have a big problem with. I don't think pushing the burden to all consumers is the best approach. Especially because I don't think a lot of people will be excited to refactor their code base and instead stick to the old API, together with complaining about how poorly HighDPI is handled by SWT...

ptziegler avatar Sep 11 '25 07:09 ptziegler

Thank you for the examples regarding Height/Width. They make sense, but as you said, we can leave details up for an actual realization.

While this is true I don't think it should limit us to make SWT better, the same arguments hold true for generics, records or maybe any API enhancement. And soon a "new" API can become an established one used at many places.

I challenge this assumptions. You may do that with a vivid library in, e.g., a major version increment, but how to you expect RCP products adapt to something like this? And as said, in any case there will be consumers of the existing API, you we as SWT developers have to maintain both the legacy and the new API, which introduces another maintenance burden.

Also we do not need everything as once and why I used setBounds / computeSize in my example

But that only works if you have a strong committment that somewill will go the complete, long way afterwards. Otherwise you will end up in a mess of inconsistent APIs.

"if you want best HiDPI support use these new methods otherwise you might see glitches"

And this is the statement I have a big problem with. I don't think pushing the burden to all consumers is the best approach. Especially because I don't think a lot of people will be excited to refactor their code base and instead stick to the old API, together with complaining about how poorly HighDPI is handled by SWT...

That's exactly what we want to avoid. Adopting enhanced HiDPI support should not become a burden for adopters. There are of course places you need to adapt to get the best experiences (most prominent example are images), but everything else should be as transparent as possible. There are limitations, for example regarding cross-monitor calculations, but we did as much as possible to handle that internal of SWT. And so far it seems to work acceptably fine. We did not face severe issues with the current state so far. Maybe that will change in the future and then we need to react, but if not, let's keep our resources for more relevant tasks.

HeikoKlare avatar Sep 11 '25 07:09 HeikoKlare

That looks way too verbose. I can also already see the boilerplate code of when you e.g want to transpose the object and therefore need to convert the width to a height and vice versa...

I really can't see much of a use case here, so honestly how often one transposes a widget?!?

Doesn't every modern IDE provide some option to show the parameter names?

Sure but its not enabled by default, sometimes not shown (I haven't found out the logic behind) and still not show up in all tools (e.g. Github PRs) or is prone to refactoring/rename changes and so on.

I honestly can't tell how much in other context such "loose" typing has ruined my day because you can stare literally a hundred of times at such line and it does not looks wrong until you realize that it is just the wrong parameter, or range or whatever.

That's exactly what we want to avoid. Adopting enhanced HiDPI support should not become a burden for adopters.

If you want your custom widget to be best represented in HighDPI you already need to adopt, let it be new Image constructors or possibly Zoom Chane events. This is unavoidable for me.

What should NOT happen is, that the usual SWT developers needs to care (e.g. on each Dialog, Shell, whatever) because we now get all kind of adjustments required due to improper internal handling of types.

We did not face severe issues with the current state so far.

Well it depends on how you define "serve" just two example that people find server enough to complain and that currently try to be fixed by changing fundamentally how SWT (and even JFace!) and Widget / Layouts works by dragging in internal SWT HighDPI concerns:

  • https://github.com/eclipse-platform/eclipse.platform.swt/pull/2381
  • https://github.com/eclipse-platform/eclipse.platform.ui/pull/3212

so at least if it drips into consumers (JFace) this shows to me something is borked.

laeubi avatar Sep 11 '25 07:09 laeubi

For Javas Temporal API, you have the java.time.temporal.ChronoUnit enum to define the unit of time. And I think something similar would find more acceptance from users. e.g. setSize(50, 25, Unit.PIXEL), which SWT can then convert into whatever internal structure would be appropriate.

I don't think the general goal is to face the actual unit into the API, this should be internal, but lets say I have measured something in pixel, then I want to apply it in pixel (internally), and maybe scale / add / substract in a value that is then (maybe) scaled e.g. if it is given by user facing API.

I challenge this assumptions. You may do that with a vivid library in, e.g., a major version increment, but how to you expect RCP products adapt to something like this?

The point is that no one needs to immediately adapt here, if we can make SWT use that API internally then users would usually not notice anything or need to get in touch (in contrast to changes like this what will affect a possibly unknown audience and has a lot of other problems). Also when adding new methods one don't need a major version increment.

And as said, in any case there will be consumers of the existing API, you we as SWT developers have to maintain both the legacy and the new API, which introduces another maintenance burden

This already is the case now with dripping all of the Monitor, Float whatever variants, just that they try to fix singular cases (that the introduce new problems) instead of trying to go forward to a more safe and future proof API.

e.g. when it comes to a Dimension class it can trivially provide asPoint() (addressing the concerns in https://github.com/eclipse-platform/eclipse.platform.swt/issues/2129 that Rectangle currently is mostly 0,0)) or asRectangle() and of course then there can be a loss in precision, but only at these points and if one is concerned then this can further enhanced, so the whole SWT get "more precise" over time instead of getting more and more case where we need to care doing things "right" and converting value back and forth.

laeubi avatar Sep 11 '25 07:09 laeubi

I really can't see much of a use case here, so honestly how often one transposes a widget?!?

Not necessarily a widget, but what about an image? The API should at the very least be consistent across the board.

Sure but its not enabled by default, sometimes not shown (I haven't found out the logic behind) and still not show up in all tools (e.g. Github PRs) or is prone to refactoring/rename changes and so on.

That to me sounds like an improperly configured development environment or bugs in the toolings. I don't think SWT should compensate for this by adding complicated API.

I honestly can't tell how much in other context such "loose" typing has ruined my day because you can stare literally a hundred of times at such line and it does not looks wrong until you realize that it is just the wrong parameter, or range or whatever.

It's not like I don't understand the issue when using ambiguous types. I just don't think adding a dedicated Width, Height or Length class is the solution to this. Just to give another example of what would be an absolute nightmare: Polymorphism.

How would you e.g. add a Width (squared) and a Height (squared) to calculate the diagonal length (squared)? I assume both classes would extend a common Length class that makes this possible. But what's the return type? A Length? Is it still a Length if you add two Widths? What even is a squared Width in this context? Probably a Dimension. But how do you then add two Dimensions...

SWT is also a very low-level framework. So all this object creation might not be the best approach in terms of performance.

There are simply a lot of open questions I see with this and as Heiko said, someone needs to be committed to this change and needs to invest a lot of time into this to get it right.

ptziegler avatar Sep 11 '25 08:09 ptziegler

Not necessarily a widget, but what about an image? The API should at the very least be consistent across the board.

Okay lets for now ignore that images are always painted and you can not transpose an Image object itself and likley wnat to use a Transform instead if you not only like trivial transformations t to be applied, it would not be to complicated to have a Dimension getting a method transpose() but this more shows how such types make things more semantic in contrast to having code like gc.drawImage(dim.y, dim.x) //remember we transpose the image because that x+y is exchanged!) what would always needs additional context versus gc.drawImage(dim.transpose()) what can be understood on its own and is typesafe.

How would you e.g. add a Width (squared) and a Height (squared) to calculate the diagonal length (squared)? I assume both classes would extend a common Length class that makes this possible. But what's the return type? A Length? Is it still a Length if you add two Widths? What even is a squared Width in this context? Probably a Dimension. But how do you then add two Dimensions...

If one is patient enough to add all kind of mathematical operation, probably yes. But we are talking here about rectangular objects that should give an x/y value at the moment so one do not need to complicate it to much for artificial cases.

SWT is also a very low-level framework. So all this object creation might not be the best approach in terms of performance.

The same argument was likely raised in the past regarding why using integers versus floats (and will arise once we realize we need doubles). But in the end all such operations are nothing that are expected to be performed multiple times and we already have caching of these values in place. In case of e.g. making animations, one might want to use low-level values and having smaller glitches or even better use direct OS calls anyways.

someone needs to be committed to this change and needs to invest a lot of time into this to get it right.

I already see a lot of work going on in that area to work around the current limitations, so it feels better to go towards a direction that ensures correctness and type safety than trying to keep exiting limited ways alive.

laeubi avatar Sep 11 '25 09:09 laeubi

Side note: I won't comment on technical details, because I have already made my point on why I consider the proposal of a semantic API rather a nice idea than something that can be realized and thus needs to be discussed in detail.

If you want your custom widget to be best represented in HighDPI you already need to adopt, let it be new Image constructors or possibly Zoom Chane events. This is unavoidable for me.

What should NOT happen is, that the usual SWT developers needs to care (e.g. on each Dialog, Shell, whatever) because we now get all kind of adjustments required due to improper internal handling of types.

Agree, and currently none of them is the case. Even custom widgets work out of the box in most cases.

Well it depends on how you define "serve" just two example that people find server enough to complain and that currently try to be fixed by changing fundamentally how SWT (and even JFace!)

Where do you see fundamental changes here? And if you consider the changes fundamental, why are your proposals less fundamental? It is about the missing precision of calculations based on integer values. That was always bad, even before any change in HiDPI support. Points values already have lower precision than pixel values on zoomed monitors for years (since the introduction of the HiDPI concept to SWT). No one seems to have complained that the existing HiDPI concept does not solve every issue related to it, as the benefits outweigh the limitations. We know that fundamentally fixing this would be an unmanagable effort, so what exactly is the alternative you propose here? Note that there is a way between not making any invention and throwing away everything because of current limitations, as we have seen throughout the last years and as we have also shown with the progress regarding monitor-specific scaling, so I would be in favor making the discussion here completely independent from challenging the progress in those topics.

Also note that I fully agree that we need to have a more holistic concept of where to use higher precision values (e.g., in points and rectangles). We are working on doing exactly that.

My conclusion on this is as follows: you are welcome to propose a holistic, completely modernized, future-proof solution, but I really hope that until you come up with it we can unblock current progress with such an expectation and agree that we focus on an incremental way of improvements and a constructive way of making progress.

HeikoKlare avatar Sep 11 '25 09:09 HeikoKlare

My conclusion on this is as follows: you are welcome to propose a holistic, completely modernized, future-proof solution, but I really hope that until you come up with it we can unblock current progress with such an expectation and agree that we focus on an incremental way of improvements and a constructive way of making progress.

I already made suggestions (e.g round up when computing a Dimension) but it seems not sufficient enough to be considered to be tried out, so I opened this issue if I maybe misunderstood and the goal is to make it possible to have pixel exact layouts as much as possible what is not due to current restriction (we can't change current API).

So I tried again to summarize here why I think the proposed fix is fixing it on the wrong place and hope it is consider constructive, I do not want to block anything but asserting "we do not want adopters to change" and on the other hand forcing usage of internal API to fix a problem in hundreds of places instead of either go for a holistic public API usable by everyone (what you say will maybe never happen) seems a no-go for me.

laeubi avatar Sep 12 '25 04:09 laeubi

One clarification: my argumentation in this issue regarding significantly changing APIs does not mean that I think that all proposed solutions for current issues (which caused the creation of this discussion) are the right thing to do. I totally understand and have the same concern regarding the issue you just linked and need some further understanding there. I would just like to treat these two paths (specific current issues and this holistic API change) as separate as possible.

HeikoKlare avatar Sep 12 '25 07:09 HeikoKlare