Closed hecrj closed 3 months ago
@hecrj could you provide some guidance on a desired API?
@jackpot51 I'm not Hector, but perhaps I can help here, as I submitted https://github.com/pop-os/cosmic-text/issues/42 which I believe is more or less a duplicate of this issue (except it requests that the Buffer be able to figure out both dimensions, not just height).
(you may also wish to read the comments on that issue, as there is more discussion than here).
I think the key thing to understand here is that UI layout algorithms may want to determine their layout based on the size of an unconstrained text layout, rather than giving the text box an explicit size ahead of time.
An obvious case is laying out a vertical page within a scroll view, where you want a paragraph of text to take up however much vertical space it needs: it doesn't matter how much space that is so long as you can know how much space that ends up being. I believe it is possible to get this information out of cosmic-text currently by manually iterating of layout lines, but it's not particularly nice or easy. It would be nice if that height was calculated during layout and stored in an easy to access property.
A less obvious, but currently more problematic case is if you want to leave the width unspecified. As documented in https://github.com/pop-os/cosmic-text/issues/42#issuecomment-1607731931, you can kind of do this by setting the width to some large value, but this breaks alignment as alignment as the alignment will be based on that large size rather than the actual with of the text. It would be much better if that width could explicitly be set to "undefined" which would then cause alignment to use the computed width.
This could be done by making set_size
on Buffer
take Option<i32>
rather than i32
(or perhaps Option<u32>
as I don't think negative dimensions make sense).
(Regarding users of cosmic-text: Bevy and Floem are both using Taffy which implements this style of layout)
CSS-style layout modes actually have two separate "undefined" layout modes - "min-content" and "max-content". Specifically for horizontal text, these should be thought of as constraints on the width of the laid out text:
max-content
layout is the intuitive "undefined" layout where text doesn't wrap at all unless an explicit new line character is encountered. The max-content width is the width of the longest line in that layout.min-content
layout is the layout where the text is wrapped at the "min-content width". The min-content width is the width of the largest unbreakable glyph run in the Buffer (where what is considered "unbreakable" depends on the specified Wrap
mode).These sizes are used as hard bounds between which the layout algorithm can decide the final size of the text-containing box.
Add the following struct:
struct SizeConstraint {
MinContent,
MaxContent,
Definite(u32),
}
Buffer
have both input_width
/input_height
and output_width
/output_height
properties.SizeConstraint
(see above). The output properties can be plain u32
s.MinContent
or MaxContent
) then text wraps according to rules above. This results in an ouput_width and an output_height (u32
).Definite
then text is wrapped to max(input_width, min-content width)
(min-content width = "width of largest unbreakable glyph run"), which also becomes the output_width.max(input_height, computed_height)
or just computed_height
is the input height is indefinite.Text is then aligned using:
MinContent
then min-content widthMaxContent
then max-content widthDefinite
then max(input_width, min-content width)
as the width.If you wanted to do it in a slightly simpler way, then you could have the input dimensions be Option<u32>
instead of SizeConstraint
(where Option::None
would follow the max-content semantics above). Min-content support could always be added later if this approach was taken.
Perhaps this is going out of scope a little, but it might be nice if the "input dimensions" weren't stored on the Buffer
at all, but were passed to the method that performs the layout. This is because in many layout systems, a text buffer doesn't have a single set of "input dimensions" (perhaps better thought of as "size constraints"), but may need to be sized repeatedly under multiple constraints to determine the final layout. But this change is much less important and doesn't block use of cosmic-text.
Thank you @nicoburns for the description, I will look into this.
I've seen cases in iced
text layout that the proposed SizeConstraint
doesn't cover (afaict):
iced::layout::Limits
) will be used if it is greater than the measured text width but less than the max width. (Although I'm not familiar with whether any arrangement of widgets could produce this case)I believe (1) would be partially covered by making the output dimension in each axis:
max(min(input size, max-content size), min-content size)
Then the UI layout can use the output width.
I'm not 100% sure how that would interact with alignment though. If you want to customise whether the alignment was based on the specified width vs. the measured width then I guess you would need some kind of extra input (either a flag or a second size).
I somewhat feel like needing to cover all these behaviors (with output size and alignment) is a motivation for having a separate measurement operation that doesn't interact with alignment at all. Thus, allowing the user to operate on the measured dimensions with any behavior desired to determine the final bounds that they pass to a function that does alignment.
Although this might be a bit out of scope. And I guess the user can already do this by just performing full layout twice, so this could just be considered a future optimization. Also, it may not easily fit in the current Buffer
API.
@nicoburns described the use cases very well. I also like the design proposed.
iced
does not have plans to support the min-content
layout approach, at least for now; so that's not a priority for us.
In iced
, we are currently rolling our own logic to measure a Buffer
:
This is what prompted me to create this issue. The sizes we initially use for the Buffer
are, as @nicoburns described, maximum boundaries; but we want the Buffer
to end up with minimum ones.
Bump
Is there any ongoing work here?
Bump
Bump
bump
This should be fixed in #271, which allows a Buffer to be created without a defined width and size. Buffer::layout_runs
can then be used to figure out the actual size. If there are follow-up changes to request, please make a new issue.
While it is possible to use
i32::MAX
if there is no height limit for aBuffer
, the resultingBuffer
will usei32::MAX
as the final dimension. This means thatBuffer::visible_lines
will not be very useful.I think it'd be more useful to let layouting dictate the final
height
of aBuffer
.