Open senneh opened 1 year ago
Can you say something about the advantages/disadvantages of the two representations?
For outputs, e.g. window.get_size()
, it's not necessary to use an enum.
For inputs, e.g. window.set_size(size)
, use of an enum is roughly equivalent to providing two variants of the method. Note that one needs to know the scale factor to convert, but a window
should know that...
...but note there are some restrictions, e.g. on Wayland the initial window size must be specified in logical pixels (the scale factor might be guessable but should be treated as unknown). Meanwhile, X11 is the other way around: the size must be specified in physical pixels (but, since the scale factor must be the same for all screens, you should at least be able to guess that and convert from logical pixels).
I think a leaner less complex API is superior. We should use logical everywhere in the API except places where it won't work. (So basically how the current API works.) Then the inner implementation can worry about how to translate the platform API.
From that it follows that an enum isn't ideal, because yes it'll mandate two paths for every method.
Additionally, we want to do the conversion the minimum number of times possible, because of floating point math error accumulation and pixel alignment. Doing as much as possible with logical units gives us the power to choose where and how many times the conversion happens.
Also, ergonomics is important. Given the thinking that logical units should be used whenever possible, it would be a lot more ergonomical to just define a convention that a bare Point
/ Size
in the API is always logical. Then have the wrapper type (or something similar) for the rare cases where physical units are used instead. This would remove the need for a lot of unwrapping, while still getting the type system to complain when the units are incorrectly used at a crossing point.
That's pretty convincing; I'm on board with doing the statically typed version instead of the enum.
There's one situation that I'm still a little concerned about (that Druid currently gets wrong): although we mostly want to use logical units, we do often want to round layout to pixel boundaries in order to ensure, for example, that 1-pixel-width lines look nice. Having a bare Point
/ Size
mean logical size makes it very tempting to just round those numbers.
Yes alignment is tricky. Druid has a partial solution, but it fails some cases and doesn't even attempt dynamic fractional scale alignment [1]. I've been thinking about this on and off, but haven't found an ultimate solution yet. It would probably be wise to write up some thoughts and get some discussion going that would result in a document describing what we would like to happen in all the various cases. For now, here are some rough thoughts that immediately come to mind.
There's the simple case of 1x and 2x scaling. Just rounding the logical units will go very far to solve the problem here.
Then there's the extremely common 1.25x, 1.75x, and friends. Just rounding logical units won't work too well. Containers could probably use scale info to perform the rounding based on physical integers. That way at least every container's origin would be aligned and thus the container could do alignment of children with just the scale info and not having to know its window coords.
[1] It gets trickier though. Because yes what about things like thin lines. Perhaps a dynamic size would be interesting. SharpLine
if you will. The logical width will depend on scale to ensure that it is always super sharp in terms of physical pixels. Although to ensure usage it would have to be very ergonomic. Perhaps some default trait methods on kurbo
shapes like expand/round/shrink_to_physical_pixels(scale)
, but with better names. Plenty of details to figure out there.
Sometimes you want the half-transparent interpolation and other times you want sharp alignment. Should probably try to gather some use case scenarios and figure out if there is a strong bias either way, because that's what the default should be.
I agree that I'm not a fan of the enum option either. The only place I can think of where we might want to give users a choice of what to pass would be at window creation, since you don't know the scale yet at that point. Depending on the content you want to show you might prefer a certain physical size over a size that's consistent on many screens. Though that's not impossible to work around by making your window invisible first and resizing if necessary.
alignment
But alignment isn't really glazier's responsibility, right? It's the responsibility of a UI toolkit built on top. (Unless you're talking about the sub compositor). I just need to know the physical size of the buffer I'm drawing on.
For a UI toolkit, allowing mixed units makes more sense: "I want a button that's 60 points wide but with a 1 pixel border". Whether that button width gets rounded up or down by one pixel doesn't matter, but losing that pixel border does.
Sometimes you want the half-transparent interpolation
Wouldn't you want sharp alignment in almost all cases for a gui?
We should use logical everywhere in the API except places where it won't work.
Sounds good. A couple of exceptions I see:
for example, that 1-pixel-width lines look nice
Yes alignment is tricky. Druid has a partial solution, but it fails some cases and doesn't even attempt dynamic fractional scale alignment [1].
This is getting way off-topic, but I basically solved it. E.g. see Dimensions::new
: it uses cast_nearest()
everywhere to convert sizes to whole numbers of physical pixels, then uses physical pixels everywhere in widget code to ensure alignment. The caveats are that widget code must pull sizes from the theme's Dimensions
type for most stuff (arguably useful anyway) and remember to scale if using hard-coded sizes.
But yes, this has nothing to do with Glazier (or Winit).
It isn't always clear whether a size is in pixel or display points. Better documentation could help, but making use of the type system would be even better.
I think there are 2 ways of going about this. Using wrapper types
Or using an enum. This is how winit does it