Open nicoburns opened 1 year ago
I haven't had a chance to review in detail, but I can answer the f64 question. For a very large scrolling area, the scroll offset would lose precision (ie not even be able to represent an integer scroll offset) around 2^24. On CPU, the speed of doing f64 arithmetic is generally the same as f32.
I should also point out that what's referred to as "Xilem" above is an experimental prototype based on SwiftUI, and doesn't represent the current proposal, which is the same as Druid.
Taffy maintainer here: I'm happy to make upstream changes that you need. Adding f64
support is feasible, if that's what you end up needing.
For a very large scrolling area, the scroll offset would lose precision (ie not even be able to represent an integer scroll offset) around 2^24.
Isn't there a way to alleviate this issue regardless of the precision of float numbers? As in, making the widgets move into the view of the scrolling area, rather than the view moving inside the scrolling area, which would eliminate any problems for widgets displayed on screen and have any float precision issue invisible outside.
Edit: hmmm maybe that fixes the view position precision but not the widget position precision?
In game dev, that strategy is called a "floating origin". I think there's a good chance it would work well here.
Yes, and we may well end up wanting to do that, partly because transforms are going to be f32 on the GPU. I'm explaining the reasoning why it was f64 in Druid, and it's still open to discussion.
The proposals here came from me looking into what it would take to integrate Taffy layout into Xilem. But nothing proposed here is really specific to the CSS style layout modes (Flexbox and CSS Grid) that Taffy implements. Nor would they commit Xilem to CSS style layout. Rather, I believe they would enable Taffy layout modes to implemented in Xilem as widgets (which could live in an external crate), in much the same way that the existing
Flex
widget is implemented in Druid.I suspect that we could make a much more streamlined system if support for associating arbitrary data (e.g. "styles") with elements such that a parent widget could access them on a child widget the chidl widget having to add support for them was implemented (ala https://github.com/linebender/druid/issues/2207). But that's a much more significant change, which I think can wait.
I have also written a prototype integration of Taffy with Iced (Iced also uses a similar layout mechanism to Druid and Xilem). And despite having to work around some limitation of Iced's system (like no
measure
method, andlayout
taking&self
rather than&mut self
), the integration actually ended up being relatively straightforward (you can see the implementation of Iced'slayout
method here (calling into Taffy from Iced), and the implementation of Taffy'sperform_child_layout
method here (calling back into Iced from Taffy)).Review of Existing Systems
Here I lay out the state of things as they are in Xilem, Druid, and Taffy.
Prerequisite Type Defintions
A
Size<T>
in Taffy is defined as:A
Size
in Druid/Xilem (kurbo) is aSize<f64>
using the above definition. For the remainder of this post I will translate this toSize<f64>
in the function signatures below for clarity.A
BoxConstraints
in Druid is defined as:An
AvailableSpace
in Taffy is defined as:A
SizingMode
in Taffy is defined as:Xilem's Existing Layout System
Druid's Layout System
Taffy's Layout System
Analysis
Trivial Differences
There are a few difference which look like they might be important, but I suspect that they are actually not:
f64
and Taffy usesf32
. Perhaps @raphlinus can comment on if/why he thinksf64
is needed, but in any case we can trivially convert between the two types (accepting the loss of precision), or if it came to it, it would be simple enough (if verbose) to extend Taffy to work withf64
. I'm going to usef32
everywhere for the remainder of this post, but it could just as easily bef64
.Option<f32>
where Druid/Xilem just usef32
. However, where Taffy usesOption::None
to represent an infinite/unset size (never usingf32::INFINITY
) Druid/Xilem usef32::INFINITY
to represent this case. Again, this is a trivial conversion that could easily be handled as part of a widget or similar.Extra data parameters
data
andenv
parameters which provide extra data. I don't quite understandenv
, but I think it's some kind of context. We will need something like that in Taffy at some point for allowing styles (particularly things like writing mode / direction) to inherit down the tree. But for now, I think we can ignore this.tree
parameter which provides access to style information, the ability to request that children size themselves, and the ability to store the final computed size and position of nodes. I think this can all be handled by the widget implementation, so again we can ignore this (although this is one place where we might later get nicer DX with tigher integration with Xilem).Comparison of functions
layout
function that implements a full layout of that node and all children and returns aSize<f32>
(modulo the aforementioned f32/f64 difference). Druid suggests that this should only be called once (but that this isn't actually enforced). Taffy only does call this method once in the usual case, but may need to call it multiple times to support baseline alignment (only if baseline alignment is actually used in the layout).measure
function (this iscompute_max_intrinsic
in Druid) which allow child nodes to compute their intrinsic (content) size(s) under the provided constraints and hints and return it to parent nodes. However, they all work slightly differently:measure
function returns:Size
)min
andmax
sizes (as a tuple)measure
function:Size
)min
ormax
size depending on theavailable_space
parametercompute_max_intrinsic
functionmax
size. Druid has no concept of a min content size.I would suggest that the concept of a "min content size" is important and should definitely be included. I would also suggest that the function should not compute both the
min
andmax
sizes at once as Xilem currently does, as this could be expensive (e.g. for a text node) and at least for CSS layout it's relatively common that only one of the sizes is required.~Whether both axis are computed together or seperately I don't have too strong an opinion about. Taffy layout modes would probably compute both either way and cache the other one the other one for future queries.~ Update: I now believe that the single-axis-at-a-time model is superior.
Comparision of function parameters
Constraint paramters
Constaint parameters have a direct relationship with the returned size and must be respected by nodes' measurement/layout functions (and/or the sizes returned will be ignored/clamped if they are not).
Size<Option<f32>>
) - it is often the case that a parent node wants to ask a child node to size itself in one dimension while treating the other dimension as fixed, effectively asking the child a question like "suppose your width is 100px, what would your height be?" (perhaps the width has already been determined in an earlier part of the algorithm). This parameter provides a way to specify those fixed dimensions.BoxConstraints
). Druid's box constraints offer a strict superset of this functionality (settingmin
=max
=some finite number
in a given dimension is equivalent to setting the known dimension in that dimension; settingmin=0
,max=Infinity
is equivalent to not setting the known dimension in that dimension). The max contraint also seems useful in it's own right. It makes sense to ask a node to size itself within a certain bounding box. Taffy has it's own version of this in theavailable_space parameter
Hint parameters
Hint parameters provide extra information that nodes may use to help choose their size. These are merely hints and may be ignored in some cases. But will likely be very helpful to allow the parent and child node to cooperatively choose a good size.
Size<f32>
): Xilem uses aproposed_size: Size<f32>
parameter, which seems to be used primarily by thev_stack
component which sizes children in order and usesproposed_size
to pass "remaining available space" which is equal to it's ownproposed_size
minus "the size of any already sized children" minus "spacing between children". I would suggest that this is replaced by a more generalavailable_space
parameter (see below).parent_size
is a finite definite pixel size. But if the parent size is unknown then this enum carries an additional hint: whether the content based size should be a "min content" or a "max content" size. Taffy doesn't have an hstack/vstack-like layout, but I think this parameter would be a good place to pass the "remaining available space" that Xilem's current v_stack widget callsproposed_size
(in this caseavailable_space
would differ fromparent_size
). I think this is useful and should be kept, however I think it is potentially confusing to couple the min/max content sizing hint with this size, so I suggest that we split this into a seperate enum parameter.Proposal for Xilem
The following type definitions are used in the propsoal below:
I propose that the Xilem widget trait has the following two methods for layout, replacing the existing
layout
andmeasure
methods:I believe this would provide a strong framework within which lots of powerful layout paradigms could be implemented. But I'm sure I haven't thought of everything and feedback and discussion is of course enouraged!