From 46722dcfc262f4b477d54cb6a895539dfb99101a Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Wed, 1 Apr 2020 05:39:04 -0700 Subject: [PATCH] Swap left and right yoga position with start and end in RTL context Summary: Changelog: [Internal] Paper swaps right and left in RTL setting, this logic is in [RCTShadowView.m](https://our.intern.facebook.com/intern/diffusion/FBS/browse/master/xplat/js/react-native-github/React/Views/RCTShadowView.m?commit=cdd504cfbee66ae0659495604c4ff7b5764a1d9e&lines=529-549). For Fabric instead of doing it during yoga props assignment, I swap the left/right with start/end just before we pass yoga nodes to layout calculation. Reviewed By: shergin Differential Revision: D20420040 fbshipit-source-id: b777f2658f56c173743b2034b8b5059e3e0c9840 --- React/Fabric/RCTSurfacePresenter.mm | 34 +++-- .../view/yoga/YogaLayoutableShadowNode.cpp | 137 +++++++++++++++++- .../view/yoga/YogaLayoutableShadowNode.h | 38 +++++ .../fabric/core/layout/LayoutContext.h | 14 ++ 4 files changed, 206 insertions(+), 17 deletions(-) diff --git a/React/Fabric/RCTSurfacePresenter.mm b/React/Fabric/RCTSurfacePresenter.mm index b98e44e1880..57048acc097 100644 --- a/React/Fabric/RCTSurfacePresenter.mm +++ b/React/Fabric/RCTSurfacePresenter.mm @@ -37,6 +37,22 @@ using namespace facebook::react; +static inline LayoutConstraints RCTGetLayoutConstraintsForSize(CGSize minimumSize, CGSize maximumSize) +{ + return { + .minimumSize = RCTSizeFromCGSize(minimumSize), + .maximumSize = RCTSizeFromCGSize(maximumSize), + .layoutDirection = RCTLayoutDirection([[RCTI18nUtil sharedInstance] isRTL]), + }; +} + +static inline LayoutContext RCTGetLayoutContext() +{ + return {.pointScaleFactor = RCTScreenScale(), + .swapLeftAndRightInRTL = + [[RCTI18nUtil sharedInstance] isRTL] && [[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL]}; +} + @interface RCTSurfacePresenter () @end @@ -147,10 +163,8 @@ using namespace facebook::react; if (!scheduler) { return minimumSize; } - LayoutContext layoutContext = {.pointScaleFactor = RCTScreenScale()}; - LayoutConstraints layoutConstraints = {.minimumSize = RCTSizeFromCGSize(minimumSize), - .maximumSize = RCTSizeFromCGSize(maximumSize), - .layoutDirection = RCTLayoutDirection([[RCTI18nUtil sharedInstance] isRTL])}; + LayoutContext layoutContext = RCTGetLayoutContext(); + LayoutConstraints layoutConstraints = RCTGetLayoutConstraintsForSize(minimumSize, maximumSize); return [scheduler measureSurfaceWithLayoutConstraints:layoutConstraints layoutContext:layoutContext surfaceId:surface.rootTag]; @@ -163,10 +177,8 @@ using namespace facebook::react; return; } - LayoutContext layoutContext = {.pointScaleFactor = RCTScreenScale()}; - LayoutConstraints layoutConstraints = {.minimumSize = RCTSizeFromCGSize(minimumSize), - .maximumSize = RCTSizeFromCGSize(maximumSize), - .layoutDirection = RCTLayoutDirection([[RCTI18nUtil sharedInstance] isRTL])}; + LayoutContext layoutContext = RCTGetLayoutContext(); + LayoutConstraints layoutConstraints = RCTGetLayoutConstraintsForSize(minimumSize, maximumSize); [scheduler constraintSurfaceLayoutWithLayoutConstraints:layoutConstraints layoutContext:layoutContext surfaceId:surface.rootTag]; @@ -298,11 +310,9 @@ using namespace facebook::react; tag:surface.rootTag]; }); - LayoutContext layoutContext = {.pointScaleFactor = RCTScreenScale()}; + LayoutContext layoutContext = RCTGetLayoutContext(); - LayoutConstraints layoutConstraints = {.minimumSize = RCTSizeFromCGSize(surface.minimumSize), - .maximumSize = RCTSizeFromCGSize(surface.maximumSize), - .layoutDirection = RCTLayoutDirection([[RCTI18nUtil sharedInstance] isRTL])}; + LayoutConstraints layoutConstraints = RCTGetLayoutConstraintsForSize(surface.minimumSize, surface.maximumSize); [scheduler startSurfaceWithSurfaceId:surface.rootTag moduleName:surface.moduleName diff --git a/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.cpp b/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.cpp index e06b43cba1b..3f4c0bdbe90 100644 --- a/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.cpp +++ b/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.cpp @@ -11,12 +11,14 @@ #include #include +#include #include #include #include #include #include #include +#include namespace facebook { namespace react { @@ -78,11 +80,9 @@ YogaLayoutableShadowNode::YogaLayoutableShadowNode( updateYogaProps(); } - // Yoga children have `yogaNode_` set as owner. At this point that yogaNode_ - // is from previous `YogaLayoutableShadowNode` that is being cloned here. - // Calling `updateYogaChildren` is needed in order to update owner of - // children. - updateYogaChildren(); + if (fragment.children) { + updateYogaChildren(); + } } void YogaLayoutableShadowNode::cleanLayout() { @@ -126,6 +126,11 @@ void YogaLayoutableShadowNode::appendChild(ShadowNode::Shared const &child) { } } +bool YogaLayoutableShadowNode::doesOwn( + YogaLayoutableShadowNode const &child) const { + return child.yogaNode_.getOwner() == &yogaNode_; +} + void YogaLayoutableShadowNode::appendChildYogaNode( YogaLayoutableShadowNode const &child) { ensureUnsealed(); @@ -258,6 +263,10 @@ void YogaLayoutableShadowNode::layoutTree( applyLayoutConstraints(yogaNode_.getStyle(), layoutConstraints); + if (layoutContext.swapLeftAndRightInRTL) { + swapLeftAndRightInTree(*this); + } + { SystraceSection s("YogaLayoutableShadowNode::YGNodeCalculateLayout"); @@ -388,5 +397,123 @@ YGConfig &YogaLayoutableShadowNode::initializeYogaConfig(YGConfig &config) { return config; } +#pragma mark - RTL left and right swapping + +void YogaLayoutableShadowNode::swapLeftAndRightInTree( + YogaLayoutableShadowNode const &shadowNode) { + swapLeftAndRightInYogaStyleProps(shadowNode); + swapLeftAndRightInViewProps(shadowNode); + + for (auto &child : shadowNode.getChildren()) { + auto const yogaLayoutableChild = + traitCast(child.get()); + if (yogaLayoutableChild && !yogaLayoutableChild->doesOwn(shadowNode)) { + swapLeftAndRightInTree(*yogaLayoutableChild); + } + } +} + +void YogaLayoutableShadowNode::swapLeftAndRightInYogaStyleProps( + YogaLayoutableShadowNode const &shadowNode) { + auto yogaStyle = shadowNode.yogaNode_.getStyle(); + + YGStyle::Edges const &position = yogaStyle.position(); + YGStyle::Edges const &padding = yogaStyle.padding(); + YGStyle::Edges const &margin = yogaStyle.margin(); + + // Swap Yoga node values, position, padding and margin. + + if (yogaStyle.position()[YGEdgeLeft] != YGValueUndefined) { + yogaStyle.position()[YGEdgeStart] = position[YGEdgeLeft]; + yogaStyle.position()[YGEdgeLeft] = YGValueUndefined; + } + + if (yogaStyle.position()[YGEdgeRight] != YGValueUndefined) { + yogaStyle.position()[YGEdgeEnd] = position[YGEdgeRight]; + yogaStyle.position()[YGEdgeRight] = YGValueUndefined; + } + + if (yogaStyle.padding()[YGEdgeLeft] != YGValueUndefined) { + yogaStyle.padding()[YGEdgeStart] = padding[YGEdgeLeft]; + yogaStyle.padding()[YGEdgeLeft] = YGValueUndefined; + } + + if (yogaStyle.padding()[YGEdgeRight] != YGValueUndefined) { + yogaStyle.padding()[YGEdgeEnd] = padding[YGEdgeRight]; + yogaStyle.padding()[YGEdgeRight] = YGValueUndefined; + } + + if (yogaStyle.margin()[YGEdgeLeft] != YGValueUndefined) { + yogaStyle.margin()[YGEdgeStart] = margin[YGEdgeLeft]; + yogaStyle.margin()[YGEdgeLeft] = YGValueUndefined; + } + + if (yogaStyle.margin()[YGEdgeRight] != YGValueUndefined) { + yogaStyle.margin()[YGEdgeEnd] = margin[YGEdgeRight]; + yogaStyle.margin()[YGEdgeLeft] = YGValueUndefined; + } + + shadowNode.yogaNode_.setStyle(yogaStyle); +} + +void YogaLayoutableShadowNode::swapLeftAndRightInViewProps( + YogaLayoutableShadowNode const &shadowNode) { + auto &typedCasting = static_cast(*shadowNode.props_); + auto &props = const_cast(typedCasting); + + // Swap border node values, borderRadii, borderColors and borderStyles. + if (props.borderRadii.topLeft.hasValue()) { + props.borderRadii.topStart = props.borderRadii.topLeft; + props.borderRadii.topLeft.clear(); + } + + if (props.borderRadii.bottomLeft.hasValue()) { + props.borderRadii.bottomStart = props.borderRadii.bottomLeft; + props.borderRadii.bottomLeft.clear(); + } + + if (props.borderRadii.topRight.hasValue()) { + props.borderRadii.topEnd = props.borderRadii.topRight; + props.borderRadii.topRight.clear(); + } + + if (props.borderRadii.bottomRight.hasValue()) { + props.borderRadii.bottomEnd = props.borderRadii.bottomRight; + props.borderRadii.bottomRight.clear(); + } + + if (props.borderColors.left.hasValue()) { + props.borderColors.start = props.borderColors.left; + props.borderColors.left.clear(); + } + + if (props.borderColors.right.hasValue()) { + props.borderColors.end = props.borderColors.right; + props.borderColors.right.clear(); + } + + if (props.borderStyles.left.hasValue()) { + props.borderStyles.start = props.borderStyles.left; + props.borderStyles.left.clear(); + } + + if (props.borderStyles.right.hasValue()) { + props.borderStyles.end = props.borderStyles.right; + props.borderStyles.right.clear(); + } + + YGStyle::Edges const &border = props.yogaStyle.border(); + + if (props.yogaStyle.border()[YGEdgeLeft] != YGValueUndefined) { + props.yogaStyle.border()[YGEdgeStart] = border[YGEdgeLeft]; + props.yogaStyle.border()[YGEdgeLeft] = YGValueUndefined; + } + + if (props.yogaStyle.border()[YGEdgeRight] != YGValueUndefined) { + props.yogaStyle.border()[YGEdgeEnd] = border[YGEdgeRight]; + props.yogaStyle.border()[YGEdgeRight] = YGValueUndefined; + } +} + } // namespace react } // namespace facebook diff --git a/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.h b/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.h index 3a70d586eda..0ee5dc091a6 100644 --- a/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.h +++ b/ReactCommon/fabric/components/view/yoga/YogaLayoutableShadowNode.h @@ -100,6 +100,12 @@ class YogaLayoutableShadowNode : public LayoutableShadowNode { mutable YGNode yogaNode_; private: + /* + * Return true if child's yogaNode's owner is this->yogaNode_. Otherwise + * returns false. + */ + bool doesOwn(YogaLayoutableShadowNode const &child) const; + /* * Appends `child`'s Yoga node to the own Yoga node. * Complements `ShadowNode::appendChild(...)` functionality from Yoga @@ -122,6 +128,38 @@ class YogaLayoutableShadowNode : public LayoutableShadowNode { YGMeasureMode widthMode, float height, YGMeasureMode heightMode); + + /* + * Walks though shadow node hierarchy and reassign following values: + * - (left|right) → (start|end) + * - margin(Left|Right) → margin(Start|End) + * - padding(Left|Right) → padding(Start|End) + * - borderTop(Left|Right)Radius → borderTop(Start|End)Radius + * - borderBottom(Left|Right)Radius → borderBottom(Start|End)Radius + * - border(Left|Right)Width → border(Start|End)Width + * - border(Left|Right)Color → border(Start|End)Color + * This is neccesarry to be backwards compatible with Paper, it swaps the + * values as well in https://fburl.com/diffusion/kl7bjr3h + */ + static void swapLeftAndRightInTree( + YogaLayoutableShadowNode const &shadowNode); + /* + * In shadow node passed as argument, reassigns following values + * - borderTop(Left|Right)Radius → borderTop(Start|End)Radius + * - borderBottom(Left|Right)Radius → borderBottom(Start|End)Radius + * - border(Left|Right)Width → border(Start|End)Width + * - border(Left|Right)Color → border(Start|End)Color + */ + static void swapLeftAndRightInViewProps( + YogaLayoutableShadowNode const &shadowNode); + /* + * In yoga node passed as argument, reassigns following values + * - (left|right) → (start|end) + * - margin(Left|Right) → margin(Start|End) + * - padding(Left|Right) → padding(Start|End) + */ + static void swapLeftAndRightInYogaStyleProps( + YogaLayoutableShadowNode const &shadowNode); }; template <> diff --git a/ReactCommon/fabric/core/layout/LayoutContext.h b/ReactCommon/fabric/core/layout/LayoutContext.h index b37bd26231d..31a926b5041 100644 --- a/ReactCommon/fabric/core/layout/LayoutContext.h +++ b/ReactCommon/fabric/core/layout/LayoutContext.h @@ -41,6 +41,20 @@ struct LayoutContext { * purpose), make sure the memory is managed responsibly. */ std::vector *affectedNodes{}; + + /* + * Flag indicating whether in reassignment of direction + * aware properties should take place. If yes, following + * reassignment will occur in RTL context. + * - (left|right) → (start|end) + * - margin(Left|Right) → margin(Start|End) + * - padding(Left|Right) → padding(Start|End) + * - borderTop(Left|Right)Radius → borderTop(Start|End)Radius + * - borderBottom(Left|Right)Radius → borderBottom(Start|End)Radius + * - border(Left|Right)Width → border(Start|End)Width + * - border(Left|Right)Color → border(Start|End)Color + */ + bool swapLeftAndRightInRTL{false}; }; } // namespace react