Repository navigation
Keep cross axis margins and relative insets of items in wrapping containers - #2045
Om-singhaI wants to merge 5 commits into
Conversation
|
@javache has imported this pull request. If you are a Meta employee, you can view this in D127132637. |
cipolleschi
left a comment
There was a problem hiding this comment.
Thanks for digging into this, and for the clear before/after table. The fix looks right to me. I built it locally, the full suite passes, and I compared a few wrap layouts against main.
My concern isn't the change itself but what it does to existing layouts. I left a couple of questions inline.
| case Align::End: | ||
| // Not yet implemented | ||
| break; | ||
| case Align::FlexStart: { |
There was a problem hiding this comment.
This is the right fix, but it moves items that apps have been laying out with since 3.0. I compared main and this PR with a small Yoga test: a row with flexWrap: 'wrap', alignItems: 'flex-start' and children with margin: 4, which is a common setup in RN. On main the children on the first line sit at top 0; with this PR they move to 4, and the second line moves from 38 to 42. center with a one-sided margin and wrap-reverse with stretch move too.
React Native runs Yoga with Errata::All by default. Would it make sense to put the old behavior behind a new Errata flag, as you offered in the summary? That way RN keeps its current layout under the classic conformance, and strict gets the spec behavior.
There was a problem hiding this comment.
Good idea, done in 75fbc35. I added YGErrataWrapCrossAlignmentIgnoresMarginAndInset (1 << 5). When it is set, STEP 8 and the wrap-reverse mirroring use their previous offsets. New configs leave it unset, and YGErrataAll and YGErrataClassic include it, so RN keeps its current layout under classic and strict gets the spec behavior. I can also set it on new configs if you prefer.
Your example now has a test: under YGErrataAll the lines stay at top 0 and 38, and under YGErrataNone they move to 4 and 42. I also laid out 20000 random trees with YGErrataAll on the base commit and on this branch, and every position matched. The enum bindings are regenerated with enums.py.
|
|
||
| // The second line spans top 40 to 100. The item keeps its margin and moves | ||
| // 10 down for its top inset under every alignment. | ||
| TEST(YogaTest, wrap_row_keeps_cross_axis_margin_and_inset) { |
There was a problem hiding this comment.
The expected values here are worked out by hand from the spec. Could we add these cases as gentest fixtures instead, for example in YGFlexWrapTest.html? Then the expected values come from Chrome, and we get the Java and JS tests for free.
There was a problem hiding this comment.
Done in 191a3ae. The cases are now fixtures, eleven in YGFlexWrapTest.html and the baseline row in YGAlignItemsTest.html, and gentest wrote the C++, Java and JS tests from them in headless Chrome. They use align-self, so one tree covers all four alignments. The twelve generated C++ tests pass on this branch and all fail without the fix. I removed the hand written spec tests they replace and kept the erratum tests hand written, since Chrome can't produce the previous layout.
| isWrapReverse ? flexEndMargin : flexStartMargin; | ||
| const float trailingCrossMargin = | ||
| isWrapReverse ? flexStartMargin : flexEndMargin; | ||
| const float relativePositionCross = child->relativePosition( |
There was a problem hiding this comment.
The summary says every case now uses the leading and trailing margins. What about margin: auto on the cross axis? In a row with flexWrap: 'wrap', an item with marginTop: 'auto' in a 50pt line lands at top 0, but at 40 without wrap, because this pass overwrites what STEP 7 computed. It's the same on main, so it's not a regression from this PR. Could you open an issue for it, or handle it here if it's small?
There was a problem hiding this comment.
Good catch, and I see the same on main. STEP 8 sets each item's cross position from its alignment and resolves auto margins to 0, so it overwrites the offset STEP 7 computed. In your setup the item lands at 0 under flex-start and stretch and at 20 under center, instead of 40. Rows with a baseline aligned item run this pass too, so they lose the margin even without wrap.
Fixing it means splitting the line's free space between the auto margins in STEP 8. That changes those layouts as well, so it would need its own errata coverage. I'd rather keep this PR to margins and insets, so I opened #2048 with the repro.
|
Thanks for the thorough follow-up, all three threads are addressed. I rebuilt the branch and checked the erratum and the fix:
One request: since the erratum keeps the current layout exactly, could you turn it on by default for new configs, as you offered? That way the behavior is preserved for everyone using Yoga, not only for With that, this looks good to me. |
|
Thanks, done in fa28ffa: the erratum is now on by default in Generated tests use a plain new config, so the twelve fixtures needed a way to opt out. c75df94 adds a |
…ainers (react#59532) Summary: Fixes react/yoga#1856 Fixes react/yoga#1067 In wrapping containers, STEP 8 (multi line alignment) of `CalculateLayout.cpp` repositions items on the cross axis and drops part of each box. In react/yoga#1856 the container is a column (Yoga's default), so `left` is on the cross axis. - `flex-start` uses `computeFlexStartPosition` (the inset) where `computeFlexStartMargin` belongs. v2.0.1 used the margin, so this is a 3.0 regression from react/yoga#1561. - `stretch`, `center` and `flex-end` never add the relative inset. - `center` centers the border box instead of the margin box, which is react/yoga#1067. Every case now uses the leading margin, trailing margin and `relativePosition`. `wrap-reverse` mirrors positions afterwards, so it leads with the flex end margin and applies the inset after mirroring, keeping each margin on its edge and `top: 10` moving the item down. Also, a percentage inset on a `flex-start` item now resolves against the container's cross size, as without wrap (`top: 10%` in a 100x200 row: 10 to 20), and baseline rows, which run this pass even without wrap, honor `bottom` and ignore insets on static items. Column container, padding 10, `flex-wrap: wrap`, default `align-content: flex-start`, item with margin 5 and `left: 10` (item left; expected worked out from the spec): | `align-items` | main | this PR | expected | |---|---|---|---| | `flex-start` | 20 | 25 | 25 | | `stretch` | 15 | 25 | 25 | | `center` | 15 | 25 | 25 | | `flex-end` | 15 | 25 | 25 | | `flex-start`, no inset | 10 | 15 | 15 | The react/yoga#1067 repro goes from top 50 to 100. ### Errata These positions have been in place for a long time, and React Native runs Yoga with `YGErrataAll`. So the previous behavior is kept behind a new erratum, `YGErrataWrapCrossAlignmentIgnoresMarginAndInset` (`1 << 5`). When it is set, STEP 8 and the `wrap-reverse` mirroring use their previous offsets. New configs leave it unset and get the fix; `YGErrataAll` and `YGErrataClassic` include it, so React Native keeps its current layout under the classic conformance, and strict gets the spec behavior. With the erratum, the `main` column of the table above is what you get. The enum bindings (C++, Kotlin and TypeScript) are regenerated with `enums.py`. ### Tests The spec cases are gentest fixtures, eleven in `YGFlexWrapTest.html` and one in `YGAlignItemsTest.html`, with expected values from Chrome and generated C++, Java and JS tests. They cover row and column, LTR and RTL, `wrap` and `wrap-reverse` (including one sided margins), baseline, static items, a percentage inset and both issue repros. All 12 generated C++ tests fail without the fix. `tests/YGFlexWrapCrossAlignmentTest.cpp` keeps 4 hand written errata cases that pin the previous positions under the erratum, including a wrapping row with `align-items: flex-start` and items with `margin: 4`, whose lines stay at top 0 and 38 under `YGErrataAll` and move to 4 and 42 under `YGErrataNone`. The positions they expect under the erratum are the ones `main` gives, and all 4 fail without the erratum checks in `CalculateLayout.cpp`. With the change, all 884 C++ tests pass (868 plus 12 generated plus 4). Laying out 20000 random trees with `YGErrataAll`, every position on this branch matches the base commit exactly, and without the erratum every position matches the first commit of this PR. Not covered here: `auto` cross margins in wrapping containers and baseline rows, which STEP 8 also ignores on `main` (react/yoga#2048). Changelog: [General][Fixed] - Keep cross axis margins and relative insets of items in wrapping containers, with a new erratum that keeps the previous positions under YGErrataAll and YGErrataClassic X-link: react/yoga#2045 Test Plan: Universal macOS Cosmo Studio app and dSYM build passed with the configuration from the failing CI job: ```bash buck2 build fbsource//xplat/mode/cosmo/auto.py#production,macosx-universal,e2e,bundle-rn --show-json-output --config apple.fb_bundle_version=1101344647 //xplat/arfx/cosmo/studio/app/mac:Cosmo "//xplat/arfx/cosmo/studio/app/mac:Cosmo[dsym]" ``` `BUILD SUCCEEDED`, exit code 0. https://www.internalfb.com/buck2/fbae0652-2658-4575-a895-b8a3d54d386f Yoga regression tests: ```bash buck2 test //xplat/yoga:tests ``` Pass 893. Fail 0. Skip 0. Infra Failure 0. Build failure 0. https://www.internalfb.com/intern/testinfra/testrun/12666374165098828 C++ stable API snapshot validation: ```bash buck2 run //xplat/js/react-native-github/scripts/cxx-api:api-snapshot -- --validate ``` Passed, exit code 0. The base includes the landed Boost Filesystem fix in D127790095. The Yoga changes are unchanged apart from merging onto the current base. App launch and Cosmo E2E execution were not tested by the local build. Reviewed By: javache Differential Revision: D127132637 Pulled By: cipolleschi
|
hey @Om-singhaI, i'm trying to merge this, but can you rebase on top of master? |
…ainers The multi line alignment pass (STEP 8) dropped the relative inset for `stretch`, `center` and `flex-end`, and for `flex-start` it used the inset in place of the leading margin. `center` also ignored margins, so an item with a one sided cross margin was centered on its border box. Baseline layouts run the same pass without wrapping, so they were affected too. Compute the leading margin, trailing margin and relative inset once per item and use them in every alignment, including baseline, which now honors `bottom` and skips insets on static items like the single line path does. Percentage insets on `flex-start` items now resolve against the container's cross size, as they do without wrapping. `flex-wrap: wrap-reverse` mirrors these positions afterwards, so there the flex end margin is the leading one, which keeps each margin on the edge it was set on. The inset is taken out before mirroring and added back afterwards so it keeps its physical direction. Fixes react#1856 Fixes react#1067
Items in wrapping containers now keep their cross axis margins and relative insets, which moves layouts that apps rely on today. Add `YGErrataWrapCrossAlignmentIgnoresMarginAndInset` (`1 << 5`) and, when it is set, give the multi line alignment pass and the `wrap-reverse` mirroring step their previous offsets, so those layouts stay exactly as they were. New configs leave the bit unset and follow the spec. `YGErrataAll` and `YGErrataClassic` include it, so React Native keeps its current layout under the classic conformance. Regenerate the enum bindings with `enums.py` and pin the previous positions in tests, including a wrapping row with `align-items: flex-start` and items with a margin of 4, whose lines stay at top 0 and 38 under the erratum.
Add the cases from YGFlexWrapCrossAlignmentTest.cpp to the YGFlexWrapTest and YGAlignItemsTest fixtures, so the expected values come from Chrome and the Java and JavaScript tests are generated too. The hand written spec tests are removed. The tests for the new erratum stay hand written, since Chrome cannot produce the previous layout.
A fixture can now name a `YGErrata` value in a `data-errata` attribute, next to `data-experiments` and `data-disabled`. The generated C++, Java and JavaScript tests then set it on their config right after creating it, with `YGConfigSetErrata`, `setErrata(YogaErrata...)` and `setErrata(Errata...)`. New configs carry some errata by default, while Chrome lays out to the spec, so a fixture that checks behavior one of those errata changes can use `data-errata="None"` to run without them. No fixture uses the attribute yet, and every generated test is unchanged.
Add `YGErrataWrapCrossAlignmentIgnoresMarginAndInset` to the default errata of new configs, next to `MinSizeUndefinedInsteadOfAuto` and `FlexFirstPassUsesRunningTotals`, so layouts on a default config stay exactly as they were. Clearing the bit gives the spec positions. The twelve wrap cross alignment fixtures check the spec positions from Chrome, so they now set `data-errata="None"`; without it all twelve generated C++ tests fail. Apart from the signature lines, the only change in the generated tests is that errata line in each of those twelve tests. `YGConfigTest` clears the new bit along with the other two defaults, and the erratum tests now check that a new config carries the bit and keeps the previous positions, and that clearing it gives the spec ones.
e2e7e15 to
0831cad
Compare
|
Rebased on main, so the branch is my five commits on top of ff54581 with no merge commit. They applied without conflicts. The erratum stays in the default errata, so the new |
Related: #1856, #1067
In wrapping containers, STEP 8 (multi line alignment) of
CalculateLayout.cpprepositions items on the cross axis and drops part of each box. In #1856 the container is a column (Yoga's default), soleftis on the cross axis.flex-startusescomputeFlexStartPosition(the inset) wherecomputeFlexStartMarginbelongs. v2.0.1 used the margin, so this is a 3.0 regression from Fix mismatched cases of inlineStart/End and flexStart/End #1561.stretch,centerandflex-endnever add the relative inset.centercenters the border box instead of the margin box, which is [React-Native]: Combining flexWrap, alignItems & wrap leads to unwanted alignment #1067.Every case now uses the leading margin, trailing margin and
relativePosition.wrap-reversemirrors positions afterwards, so it leads with the flex end margin and applies the inset after mirroring, keeping each margin on its edge andtop: 10moving the item down.Also, a percentage inset on a
flex-startitem now resolves against the container's cross size, as without wrap (top: 10%in a 100x200 row: 10 to 20), and baseline rows, which run this pass even without wrap, honorbottomand ignore insets on static items.New configs keep the previous positions through the erratum below, so this applies once the erratum is cleared.
Column container, padding 10,
flex-wrap: wrap, defaultalign-content: flex-start, item with margin 5 andleft: 10(item left; expected worked out from the spec):align-itemsflex-startstretchcenterflex-endflex-start, no insetThe #1067 repro goes from top 50 to 100.
Errata
These positions have been in place for a long time, so the previous behavior is kept behind a new erratum,
YGErrataWrapCrossAlignmentIgnoresMarginAndInset(1 << 5). When it is set, STEP 8 and thewrap-reversemirroring use their previous offsets, and themaincolumn of the table above is what you get. It is set by default on new configs, likeMinSizeUndefinedInsteadOfAutoandFlexFirstPassUsesRunningTotals, andYGErrataAllandYGErrataClassicinclude it, so existing layouts keep their positions. Clearing the bit, or settingYGErrataNone, gives the spec behavior.The enum bindings (C++, Kotlin and TypeScript) are regenerated with
enums.py.Tests
The spec cases are gentest fixtures, eleven in
YGFlexWrapTest.htmland one inYGAlignItemsTest.html, with expected values from Chrome and generated C++, Java and JS tests. They cover row and column, LTR and RTL,wrapandwrap-reverse(including one sided margins), baseline, static items, a percentage inset and both issue repros.Generated tests use a plain new config, which now carries the erratum, so gentest has a new
data-errataattribute: a fixture names aYGErratavalue and its generated tests set it on the config. The twelve fixtures usedata-errata="None", and every other fixture generates the same output as before. All 12 generated C++ tests fail on the base commit, and also fail on this branch without the attribute.tests/YGFlexWrapCrossAlignmentTest.cppkeeps 4 hand written errata cases that pin the previous positions under the erratum, including a wrapping row withalign-items: flex-startand items withmargin: 4, whose lines stay at top 0 and 38 on a new config and underYGErrataAll, and move to 4 and 42 when the bit is cleared or underYGErrataNone. The positions they expect under the erratum are the onesmaingives, and all 4 fail without the erratum checks inCalculateLayout.cpp.With the change, all 893 C++ tests pass (877 plus 12 generated plus 4). Laying out 20000 random trees, every position on this branch matches the base commit exactly on a new config and under
YGErrataAll, and underYGErrataNoneevery position matches the first commit of this PR.Not covered here:
autocross margins in wrapping containers and baseline rows, which STEP 8 also ignores onmain(#2048).Changelog:
[General][Fixed] - Keep cross axis margins and relative insets of items in wrapping containers when YGErrataWrapCrossAlignmentIgnoresMarginAndInset is cleared (it is set by default, so existing layouts do not change)