-
Notifications
You must be signed in to change notification settings - Fork 115
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
Support Morphorm/Subform layout #308
Comments
Wow, that's a very comprehensive summary of Morphorm! So I think there's some things I should point out that might be helpful.
I'm very curious to understand what you mean by 'lazily sizing children'? Known BugsFor the actual algorithm there are some bugs that you should be aware of. Mainly 2 things:
New AlgorithmSo, now should mention that for a little while I've been working on a new and improved version of morphorm. It's not finished, but it already solves some of the problems you've identified. The code is on this branch: https://github.com/vizia/morphorm/tree/new-layout-no-wrap. The 'new' versions are files ending with a '2', e.g. layout2.rs. Sorry about the mess in the actual code, it's WIP. The main difference in this branch is that the layout algorithm is now recursive instead of iterative. This means that phases 2 and 3 from before are combined into a single function. This eliminates the need for a The As you'll see, the new algorithm isn't finished yet. I'd say it's about 80% there and even already fixes the content-size bug I mentioned before, but is still missing the min/max size handling, grid, and extensive testing. I'm also not sure I'm handling content_size correctly as I can imagine situations where circular dependencies pop up and I can't yet think of an ideal solution (other than to hope users don't do that). I do think though that this new version will be easier to port to taffy. I will of course help with the porting effort, and let me know if you've got any questions. |
@geom3trik Thanks for pointing me to the Having said that, it does concern me somewhat that this version has slightly different behaviour and user-facing API (e.g. renamed styles) to the existing version. I was under the impression that there was more of a standard being followed, but it seems that it's more of a case of "the implementation is the specification"? In which case I think we'll need really nail down exactly how the algorithm ought to behave in corner cases, and try to be careful not to introduce discrepancies between the implementation in the I think that ideally we should:
On style property changes
I wonder how you'd feel about reverting this for the time being? My reasons being:
Regarding Content SizingA note on terminology: also like to use "content size". But you find it useful to know that CSS tends to call this "intrinsic size" https://developer.mozilla.org/en-US/docs/Glossary/Intrinsic_Size
I think these two things are broadly the same. By lazily sizing children I mean that the content sizes of nodes aren't computed in advance. Instead we start layout computation at the top of the tree, and parent node query the size of their children (passing in any relevant constraints) as required. These children can in turn query the sizes of their children, recursing down the tree as necessary. In Taffy 0.3, it's a mutually recursive algorithm rather than a simple recursion, as a child node may be sized using a different algorithm. #326 introduces a nice innovation in this model by adding a
Yes, Taffy simply has a single Regarding the
|
So the original implementation comes from an application called Subform which no longer exists. Someone on the bevy discord linked to this post https://subformapp.com/articles/why-not-flexbox/, which inspired me to implement from scratch the features shown in the marketing material on that site. Which is to say, as far as I know there is no specification and I did not find any code to use as reference. I based the orignal algorithm on a once up/once down approach as described in a video I saw explaining the flutter layout algorithm, but using iterators instead of recursion (which as you know I've since changed). The original subform layout used direction-agnostic properties, which I changed to
This might be something easier to decide when some of the other parts have been figured out. I don't particularly like the idea of depreciating my own crate but I can see the advantages to having taffy handle flexbox, morphorm, and grid in one place.
I'm not sure I fully understand what you mean here. Are you meaning that you'd like for users to be able to use the same properties, like
We're talking about changes between taffy and morphorm here?
I definitely agree with you here, but I'm concerned that not having the properties as direction agnostic would make it too restrictive if you want a layout that can adapt to direction changes. But I'm curious how this is currently handled in taffy? Because flexbox is also direction agnostic but margins/padding are not right? I'm not against changing it back to the non-direction-agnostic properties though. Users of vizia are already accustomed to the
Ah, thanks for the info. I've seen that term used before but did not realise it was what I was calling content-size.
This sounds like what I was trying to do with
This sounds intriguing and I will definitely check it out and give my thoughts. Something I haven't implemented yet with my current
I think I would agree with you here. And if morphorm is eventually incorporated into taffy then taffy already has what looks like a very robust grid implementation. The only counter-argument I would have is that it would be nice for users using morphorm to be able to use the morphorm stretch type with grids, but that's subjective.
I agree here too. I've found wrapping quite challenging to implement because of all the interdependencies between the axes of a node, particularly when trying to compute the stretch space and size combined with the auto size 'hugging' of the parent. |
Yeah, I couldn't find a specification either. There is https://github.com/lynaghk/subform-layout as a reference implementation, but it's obfuscated and in any case not under an OSS licence.
Oh interesting. A specification would be great, as would a test suite - preferrably in a declarative machine-parsable format (HTML/XML?) that we could parse to generate tests for Taffy. But I wouldn't consider either blocking.
Yeah, fair enough. I also don't think it would be fair to expect you to. You would obviously be welcome to come and maintain the implementation in Taffy. But I suspect it would make it harder to experiment and evolve to algorithm, and would come additional levels of scrutiny and review.
No, I agree that separate properties seem reasonable (although some like
Taffy skips measuring a node's contents if that node's width/height property can be resolved to a fixed size, (e.g. it has a pixel size, or a percentage size and the container has a defined size in that axis) then that size will be used). It also caches measured sizes (and the inputs used to generate those), and will simply return the value from the cache if possible. Although in most cases, it does still need to recurse through every node in the tree at least once to "perform layout". Because even if we know their size, children still ultimately need to position their own descendants.
So, CSS doesn't define If it were up to me, I'd probably initially implement Morphorm in Taffy with "absolute" properties (left, right, etc), and then look at implementing Out of interest, what is the use case for RowReverse/ColumnReverse? I don't think I've ever used these personally. Or are you more worried about switching between Row and Column? |
Ah yes I do remember finding that, but I chose not to use it because of the license. In the end the only thing I really took from the subform stuff is the concept of stretch space and size as well as the
I can write up a specification based partly on the morphorm implementation and partly on how I would like it to work. A test suite would be great, and I'm implementing some tests as I rewrite the algorithm but something auto-generated from a declarative format would be great!
Oh I see what you mean. Well hopefully a specification will solve this problem. Then both implementations (if it ends up being separate) can follow the same spec.
Right yeh, those sound like good optimisations.
Ah I was not aware of the
This was my attempt to allow the user to specify the direction that elements should be stacked for localized layouts, e.g. right-to-left locales, but it looks like |
In discussions with @geom3trik it has become apparent that Morphorm's algorithm is not as fixed/finished as I initially assumed it to be (it is based on Subform layout, but that was also never formally specified (at least not publically), and that part of this task will likely be to pin down exactly how the algorithm ought to work. Also through those discussions (and further influenced by @hecrj's push back on integrating Taffy's existing layout modes into Iced), it has become clear to me that the clearest justification for Morphorm's existence in Taffy (more so even than it's simplified mental model) is as a single-pass counterpoint to the CSS algorithms (Flexbox and CSS Grid). That is, a morphorm container should never call With this in mind, I now think that we ought to make Morphorm layout modes as fully-featured as possible (so in particular, I am now considering things wrapping and grid layout as back in scope) under the hard constraint of being single-pass, and the fuzzier constraint of having a simple mental model. This will enable systems that want utmost layout performance (or that don't want to deal with the caching required for fast multipass layout) to use Taffy by disabling the slower CSS modes, although we should also attempt to make these two groups of layout modes work together so that systems that want to enable all Taffy layout modes can do so. In implementing this we may wish to take inspiration from Flutter's layout model implements just such a single-pass layout model (it turns out that while Flutter includes a flexbox-like layout mode, it isn't actually flexbox compatible precisely because it is a single-pass variant on the same general idea). Reading on Flutter's layout model: |
Very nice writeup and goals. I'm in favor of both "simple mental model" and "fully featured", so I'm on board with this direction. |
Now that CSS Grid support is nearing completion, I thought I'd take a look at what it would take to support Morphorm layout in Taffy (CC: @geom3trik). My findings are summarised below:
Morphorm Styles
Unique Types
LayoutType
(corresponds toDisplay
in Taffy):PositionType
(corresponds toPosition
in Taffy):Units
(corresponds toDimension
in Taffy):Style Properties
Using these types (mostly
Units
), Morphorm has the following style properties. Note:Units
actually support all variants, for exampleborder
(and I think alsomin_size
andmax_size
) only really supports "LengthPercentage" (Auto and Stretch resolve to zero).layout_mode
LayoutType
flex_direction
position_type
Position
position
size
Size<Units>
size
min_size
Size<Units>
min_size
max_size
Size<Units>
max_size
border
Rect<Units>
border
child_spacing
Rect<Units>
padding
row_between
Units
gap.height
col_between
Units
gap.width
grid_rows
Vec<Units>
grid_template_rows
grid_cols
Vec<Units>
grid_tempalte_columns
spacing
Rect<Units>
margin
min_spacing
Rect<Units>
max_spacing
Rect<Units>
row_index
usize
grid_row.start
col_index
usize
grid_column.start
row_span
usize
grid_row.end
col_span
usize
grid_column.end
Analysis
Nearly everything in Morphorm is simplified version of either Flexbox or CSS Grid. There are really three things that aren't:
child_spacing
and*_between
properties. CSS does not allow you to specify child margins on the parent like this. It does have thealign-items
andjustify-items
properties, but those are not as powerful.min_spacing
andmax_spacing
properties. CSS does not allow you to specify min or max values for margins.Stretch
variant ofUnits
. Flexbox/CSS Grid do not allow you to express "fill available space" as a size like this. To achieve this with Flexbox/CSS Grid you need to use special properties that are specified separately.The only really tricky thing here is the
width
/height
properties. They are common to all algorithms, and furthermore by both parent and child nodes access this property. This means that the type of these properties really needs to be a single unified type. I thought this might be a blocker, however I have discovered that an upcoming CSS standard (css-sizing-4) actually does define this functionality and in fact even calls itstretch
. The CSS version doesn't have the weighting parameter (although I have opened an issue proposing that it does - no idea if that's the right place to do that though) so it is roughly equivalent toStretch(1.0)
, but that would a pretty straightforward extension.Algorithm and Code Structure Differences
Morphorm is relative small and simple compared to Taffy's layout algorithms (yay!). Morphorm's
layout.rs
which contains the core implementation for both it's Grid and Row/Column algorithms is just over 1000 LoC. For comparison, Taffy's Flexbox implementation alone is around ~1700 LoC, and the CSS Grid implementation almost double that.Morphorm uses a 3 phased approach to layout:
In contrast, Taffy uses a single phase approach which essentially corresponds to Phase 3 of Morphorm's approach. It also does things equivalent to Morphorm's Phase 2 (probably Phase 1 as well), but it does this lazily on demand.
Morphorm has fairly large/extensive persistent
Cache
object (See the trait and the reference implementation) which I believe it uses to store both intermediate computations and the output of it's layout. In contrast, Taffy stores it's intermediate computations locally within the computation function(s) and throws them away once it's done computing the size for a node. I have a feeling that the easiest way to get Morphorm to work with Taffy might be to make it work more like Taffy's other algorithms do and keep more of it's intermediate results internal: IMO keeping API surface small is going to be key to making this project feasible.Morphorm's layout algorithms themselves obviously have differences (that being the whole point of supporting it!), but I don't think any of that (other than the already discussed
Stretch
variant ofUnits
) is particularly relevant from an integration point of view. Taffy is already setup to let each container node have free reign over their children, so it should be straightforward to slip Morphorm's algorithms into that model.Implementation Proposal
My suggestion based on looking into this is that we don't attempt to use the
morphorm
crate as a dependency, and instead port the Morphorm code/algorithm into Taffy directly (but I'd definitely be interested to hear @geom3trik's opinion on this). This is based on the following:More specifically, I would propose the following implementation plan:
Add a
Stretch(f32)
variant to Taffy's existing style system. Making it available on thewidth
/height
properties and implementing this variant for Flexbox/CSS Grid according to https://www.w3.org/TR/css-sizing-4/#stretch-fit-sizing (except with added support for weighting), and maybe on themargin
and/orposition
properties (the latter two could be done later though).Create a
morphorm
feature flag.Add Morphorm style properties to
Style
(behind the feature flag). This will also involve working how to alter theDisplay
type (e.g. do we addMorphormRow
/MorphormColumn
/MorphormGrid
separately, or do we have a singleMorphorm
variant and sub-differentiate separately?)Implement the Morphorm layout algorithms themselves, refactoring them to fit Taffy's structure as they are ported.
Consider improving Taffy's extension trait system (i.e.
LayoutTree
andMeasureFunc
) with inspiration from Morphorm's traits (Node
,Hierarchy
,Cache
). Possibly using the approach of "just having a Node trait" as proposed here Support multiple layout algorithms #28 (comment)Implement a fracturing of the
Style
into smaller pieces similar to the one Bevy UI is about to implement.I'd love to know others' thoughts, but I'm quite encourage by what I'm seen: this actually looks quite achievable to me!
P.S. I also did some preliminary research on Flutter and Swift UI's layout systems, but I think that to a large degree their systems amount to being layout-agnostic, with each Component being able to implement arbitrary layout algorithms. So I think support for those systems will look more like "an extension point that allows custom algorithm" than an implementation of specific algorithm(s).
The text was updated successfully, but these errors were encountered: