diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_YGValueTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_YGValueTests.m new file mode 100644 index 00000000000000..ac476ab48c289b --- /dev/null +++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTConvert_YGValueTests.m @@ -0,0 +1,46 @@ +/** + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import + +#import +#import + +@interface RCTConvert_YGValueTests : XCTestCase + +@end + +@implementation RCTConvert_YGValueTests + +- (void)testUndefined +{ + YGValue value = [RCTConvert YGValue:nil]; + XCTAssertEqual(value.unit, YGUnitUndefined); +} + +- (void)testNumberPoints +{ + YGValue value = [RCTConvert YGValue:@100]; + XCTAssertEqual(value.unit, YGUnitPixel); + XCTAssertEqual(value.value, 100); +} + +- (void)testStringPercent +{ + YGValue value = [RCTConvert YGValue:@"100%"]; + XCTAssertEqual(value.unit, YGUnitPercent); + XCTAssertEqual(value.value, 100); +} + +@end diff --git a/Libraries/StyleSheet/LayoutPropTypes.js b/Libraries/StyleSheet/LayoutPropTypes.js index 95bb430a85033f..2069434129f8ed 100644 --- a/Libraries/StyleSheet/LayoutPropTypes.js +++ b/Libraries/StyleSheet/LayoutPropTypes.js @@ -30,184 +30,256 @@ var LayoutPropTypes = { /** `width` sets the width of this component. * * It works similarly to `width` in CSS, but in React Native you - * must use logical pixel units, rather than percents, ems, or any of that. + * must use points or percentages. Ems and other units are not supported. * See https://developer.mozilla.org/en-US/docs/Web/CSS/width for more details. */ - width: ReactPropTypes.number, + width: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** `height` sets the height of this component. * * It works similarly to `height` in CSS, but in React Native you - * must use logical pixel units, rather than percents, ems, or any of that. + * must use points or percentages. Ems and other units are not supported. * See https://developer.mozilla.org/en-US/docs/Web/CSS/height for more details. */ - height: ReactPropTypes.number, + height: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** `top` is the number of logical pixels to offset the top edge of * this component. * - * It works similarly to `top` in CSS, but in React Native you must - * use logical pixel units, rather than percents, ems, or any of that. + * It works similarly to `top` in CSS, but in React Native you + * must use points or percentages. Ems and other units are not supported. * * See https://developer.mozilla.org/en-US/docs/Web/CSS/top * for more details of how `top` affects layout. */ - top: ReactPropTypes.number, + top: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** `left` is the number of logical pixels to offset the left edge of * this component. * - * It works similarly to `left` in CSS, but in React Native you must - * use logical pixel units, rather than percents, ems, or any of that. + * It works similarly to `left` in CSS, but in React Native you + * must use points or percentages. Ems and other units are not supported. * * See https://developer.mozilla.org/en-US/docs/Web/CSS/left * for more details of how `left` affects layout. */ - left: ReactPropTypes.number, + left: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** `right` is the number of logical pixels to offset the right edge of * this component. * - * It works similarly to `right` in CSS, but in React Native you must - * use logical pixel units, rather than percents, ems, or any of that. + * It works similarly to `right` in CSS, but in React Native you + * must use points or percentages. Ems and other units are not supported. * * See https://developer.mozilla.org/en-US/docs/Web/CSS/right * for more details of how `right` affects layout. */ - right: ReactPropTypes.number, + right: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** `bottom` is the number of logical pixels to offset the bottom edge of * this component. * - * It works similarly to `bottom` in CSS, but in React Native you must - * use logical pixel units, rather than percents, ems, or any of that. + * It works similarly to `bottom` in CSS, but in React Native you + * must use points or percentages. Ems and other units are not supported. * * See https://developer.mozilla.org/en-US/docs/Web/CSS/bottom * for more details of how `bottom` affects layout. */ - bottom: ReactPropTypes.number, + bottom: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** `minWidth` is the minimum width for this component, in logical pixels. * * It works similarly to `min-width` in CSS, but in React Native you - * must use logical pixel units, rather than percents, ems, or any of that. + * must use points or percentages. Ems and other units are not supported. * * See https://developer.mozilla.org/en-US/docs/Web/CSS/min-width * for more details. */ - minWidth: ReactPropTypes.number, + minWidth: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** `maxWidth` is the maximum width for this component, in logical pixels. * * It works similarly to `max-width` in CSS, but in React Native you - * must use logical pixel units, rather than percents, ems, or any of that. + * must use points or percentages. Ems and other units are not supported. * * See https://developer.mozilla.org/en-US/docs/Web/CSS/max-width * for more details. */ - maxWidth: ReactPropTypes.number, + maxWidth: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** `minHeight` is the minimum height for this component, in logical pixels. * * It works similarly to `min-height` in CSS, but in React Native you - * must use logical pixel units, rather than percents, ems, or any of that. + * must use points or percentages. Ems and other units are not supported. * * See https://developer.mozilla.org/en-US/docs/Web/CSS/min-height * for more details. */ - minHeight: ReactPropTypes.number, + minHeight: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** `maxHeight` is the maximum height for this component, in logical pixels. * * It works similarly to `max-height` in CSS, but in React Native you - * must use logical pixel units, rather than percents, ems, or any of that. + * must use points or percentages. Ems and other units are not supported. * * See https://developer.mozilla.org/en-US/docs/Web/CSS/max-height * for more details. */ - maxHeight: ReactPropTypes.number, + maxHeight: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** Setting `margin` has the same effect as setting each of * `marginTop`, `marginLeft`, `marginBottom`, and `marginRight`. * See https://developer.mozilla.org/en-US/docs/Web/CSS/margin * for more details. */ - margin: ReactPropTypes.number, + margin: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** Setting `marginVertical` has the same effect as setting both * `marginTop` and `marginBottom`. */ - marginVertical: ReactPropTypes.number, + marginVertical: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** Setting `marginHorizontal` has the same effect as setting * both `marginLeft` and `marginRight`. */ - marginHorizontal: ReactPropTypes.number, + marginHorizontal: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** `marginTop` works like `margin-top` in CSS. * See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-top * for more details. */ - marginTop: ReactPropTypes.number, + marginTop: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** `marginBottom` works like `margin-bottom` in CSS. * See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-bottom * for more details. */ - marginBottom: ReactPropTypes.number, + marginBottom: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** `marginLeft` works like `margin-left` in CSS. * See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-left * for more details. */ - marginLeft: ReactPropTypes.number, + marginLeft: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** `marginRight` works like `margin-right` in CSS. * See https://developer.mozilla.org/en-US/docs/Web/CSS/margin-right * for more details. */ - marginRight: ReactPropTypes.number, + marginRight: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** Setting `padding` has the same effect as setting each of * `paddingTop`, `paddingBottom`, `paddingLeft`, and `paddingRight`. * See https://developer.mozilla.org/en-US/docs/Web/CSS/padding * for more details. */ - padding: ReactPropTypes.number, + padding: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** Setting `paddingVertical` is like setting both of * `paddingTop` and `paddingBottom`. */ - paddingVertical: ReactPropTypes.number, + paddingVertical: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** Setting `paddingHorizontal` is like setting both of * `paddingLeft` and `paddingRight`. */ - paddingHorizontal: ReactPropTypes.number, + paddingHorizontal: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** `paddingTop` works like `padding-top` in CSS. * See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-top * for more details. */ - paddingTop: ReactPropTypes.number, + paddingTop: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** `paddingBottom` works like `padding-bottom` in CSS. * See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-bottom * for more details. */ - paddingBottom: ReactPropTypes.number, + paddingBottom: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** `paddingLeft` works like `padding-left` in CSS. * See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-left * for more details. */ - paddingLeft: ReactPropTypes.number, + paddingLeft: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** `paddingRight` works like `padding-right` in CSS. * See https://developer.mozilla.org/en-US/docs/Web/CSS/padding-right * for more details. */ - paddingRight: ReactPropTypes.number, + paddingRight: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** `borderWidth` works like `border-width` in CSS. * See https://developer.mozilla.org/en-US/docs/Web/CSS/border-width @@ -365,7 +437,10 @@ var LayoutPropTypes = { flex: ReactPropTypes.number, flexGrow: ReactPropTypes.number, flexShrink: ReactPropTypes.number, - flexBasis: ReactPropTypes.number, + flexBasis: ReactPropTypes.oneOfType([ + ReactPropTypes.number, + ReactPropTypes.string, + ]), /** * Aspect ratio control the size of the undefined dimension of a node. Aspect ratio is a diff --git a/React/Base/RCTConvert.h b/React/Base/RCTConvert.h index f725092c49ce51..117d990a6ebfcc 100644 --- a/React/Base/RCTConvert.h +++ b/React/Base/RCTConvert.h @@ -10,13 +10,13 @@ #import #import -#import #import #import #import #import #import #import +#import /** * This class provides a collection of conversion functions for mapping @@ -90,6 +90,8 @@ typedef NSURL RCTFileURL; + (UIColor *)UIColor:(id)json; + (CGColorRef)CGColor:(id)json CF_RETURNS_NOT_RETAINED; ++ (YGValue)YGValue:(id)json; + + (NSArray *)NSArrayArray:(id)json; + (NSArray *)NSStringArray:(id)json; + (NSArray *> *)NSStringArrayArray:(id)json; diff --git a/React/Base/RCTConvert.m b/React/Base/RCTConvert.m index 4036be60063560..c834d16644e944 100644 --- a/React/Base/RCTConvert.m +++ b/React/Base/RCTConvert.m @@ -496,6 +496,25 @@ + (CGColorRef)CGColor:(id)json return [self UIColor:json].CGColor; } ++ (YGValue)YGValue:(id)json +{ + if (!json) { + return YGValueUndefined; + } else if ([json isKindOfClass:[NSNumber class]]) { + return (YGValue) { [json floatValue], YGUnitPixel }; + } else if ([json isKindOfClass:[NSString class]]) { + NSString *s = (NSString *) json; + if ([s hasSuffix:@"%"]) { + return (YGValue) { [[s substringToIndex:s.length] floatValue], YGUnitPercent }; + } else { + RCTLogConvertError(json, @"a YGValue. Did you forget the % or pt suffix?"); + } + } else { + RCTLogConvertError(json, @"a YGValue."); + } + return YGValueUndefined; +} + NSArray *RCTConvertArrayValue(SEL type, id json) { __block BOOL copy = NO; diff --git a/React/Views/RCTShadowView.h b/React/Views/RCTShadowView.h index d9b5667cfc9f4d..b1322cbabf80f4 100644 --- a/React/Views/RCTShadowView.h +++ b/React/Views/RCTShadowView.h @@ -9,9 +9,9 @@ #import -#import #import #import +#import @class RCTSparseArray; @@ -66,18 +66,18 @@ typedef void (^RCTApplierBlock)(NSDictionary *viewRegistry * Position and dimensions. * Defaults to { 0, 0, NAN, NAN }. */ -@property (nonatomic, assign) float top; -@property (nonatomic, assign) float left; -@property (nonatomic, assign) float bottom; -@property (nonatomic, assign) float right; +@property (nonatomic, assign) YGValue top; +@property (nonatomic, assign) YGValue left; +@property (nonatomic, assign) YGValue bottom; +@property (nonatomic, assign) YGValue right; -@property (nonatomic, assign) float width; -@property (nonatomic, assign) float height; +@property (nonatomic, assign) YGValue width; +@property (nonatomic, assign) YGValue height; -@property (nonatomic, assign) float minWidth; -@property (nonatomic, assign) float maxWidth; -@property (nonatomic, assign) float minHeight; -@property (nonatomic, assign) float maxHeight; +@property (nonatomic, assign) YGValue minWidth; +@property (nonatomic, assign) YGValue maxWidth; +@property (nonatomic, assign) YGValue minHeight; +@property (nonatomic, assign) YGValue maxHeight; @property (nonatomic, assign) CGRect frame; @@ -102,24 +102,24 @@ typedef void (^RCTApplierBlock)(NSDictionary *viewRegistry /** * Margin. Defaults to { 0, 0, 0, 0 }. */ -@property (nonatomic, assign) float margin; -@property (nonatomic, assign) float marginVertical; -@property (nonatomic, assign) float marginHorizontal; -@property (nonatomic, assign) float marginTop; -@property (nonatomic, assign) float marginLeft; -@property (nonatomic, assign) float marginBottom; -@property (nonatomic, assign) float marginRight; +@property (nonatomic, assign) YGValue margin; +@property (nonatomic, assign) YGValue marginVertical; +@property (nonatomic, assign) YGValue marginHorizontal; +@property (nonatomic, assign) YGValue marginTop; +@property (nonatomic, assign) YGValue marginLeft; +@property (nonatomic, assign) YGValue marginBottom; +@property (nonatomic, assign) YGValue marginRight; /** * Padding. Defaults to { 0, 0, 0, 0 }. */ -@property (nonatomic, assign) float padding; -@property (nonatomic, assign) float paddingVertical; -@property (nonatomic, assign) float paddingHorizontal; -@property (nonatomic, assign) float paddingTop; -@property (nonatomic, assign) float paddingLeft; -@property (nonatomic, assign) float paddingBottom; -@property (nonatomic, assign) float paddingRight; +@property (nonatomic, assign) YGValue padding; +@property (nonatomic, assign) YGValue paddingVertical; +@property (nonatomic, assign) YGValue paddingHorizontal; +@property (nonatomic, assign) YGValue paddingTop; +@property (nonatomic, assign) YGValue paddingLeft; +@property (nonatomic, assign) YGValue paddingBottom; +@property (nonatomic, assign) YGValue paddingRight; - (UIEdgeInsets)paddingAsInsets; @@ -135,7 +135,7 @@ typedef void (^RCTApplierBlock)(NSDictionary *viewRegistry @property (nonatomic, assign) float flexGrow; @property (nonatomic, assign) float flexShrink; -@property (nonatomic, assign) float flexBasis; +@property (nonatomic, assign) YGValue flexBasis; @property (nonatomic, assign) float aspectRatio; diff --git a/React/Views/RCTShadowView.m b/React/Views/RCTShadowView.m index 1ad9accf97c8c8..76bbff1282c70f 100644 --- a/React/Views/RCTShadowView.m +++ b/React/Views/RCTShadowView.m @@ -41,9 +41,9 @@ @implementation RCTShadowView BOOL _recomputeMargin; BOOL _recomputeBorder; BOOL _didUpdateSubviews; - float _paddingMetaProps[META_PROP_COUNT]; - float _marginMetaProps[META_PROP_COUNT]; - float _borderMetaProps[META_PROP_COUNT]; + YGValue _paddingMetaProps[META_PROP_COUNT]; + YGValue _marginMetaProps[META_PROP_COUNT]; + YGValue _borderMetaProps[META_PROP_COUNT]; } @synthesize reactTag = _reactTag; @@ -56,20 +56,42 @@ static void RCTPrint(YGNodeRef node) printf("%s(%zd), ", shadowView.viewName.UTF8String, shadowView.reactTag.integerValue); } -#define DEFINE_PROCESS_META_PROPS(type) \ -static void RCTProcessMetaProps##type(const float metaProps[META_PROP_COUNT], YGNodeRef node) { \ - YGNodeStyleSet##type(node, YGEdgeStart, metaProps[META_PROP_LEFT]); \ - YGNodeStyleSet##type(node, YGEdgeEnd, metaProps[META_PROP_RIGHT]); \ - YGNodeStyleSet##type(node, YGEdgeTop, metaProps[META_PROP_TOP]); \ - YGNodeStyleSet##type(node, YGEdgeBottom, metaProps[META_PROP_BOTTOM]); \ - YGNodeStyleSet##type(node, YGEdgeHorizontal, metaProps[META_PROP_HORIZONTAL]); \ - YGNodeStyleSet##type(node, YGEdgeVertical, metaProps[META_PROP_VERTICAL]); \ - YGNodeStyleSet##type(node, YGEdgeAll, metaProps[META_PROP_ALL]); \ +#define RCT_SET_YGVALUE(ygvalue, setter, ...) \ +switch (ygvalue.unit) { \ + case YGUnitUndefined: \ + setter(__VA_ARGS__, YGUndefined); \ + break; \ + case YGUnitPixel: \ + setter(__VA_ARGS__, ygvalue.value); \ + break; \ + case YGUnitPercent: \ + setter##Percent(__VA_ARGS__, ygvalue.value); \ + break; \ +} + +#define DEFINE_PROCESS_META_PROPS(type) \ +static void RCTProcessMetaProps##type(const YGValue metaProps[META_PROP_COUNT], YGNodeRef node) { \ + RCT_SET_YGVALUE(metaProps[META_PROP_LEFT], YGNodeStyleSet##type, node, YGEdgeStart); \ + RCT_SET_YGVALUE(metaProps[META_PROP_RIGHT], YGNodeStyleSet##type, node, YGEdgeEnd); \ + RCT_SET_YGVALUE(metaProps[META_PROP_TOP], YGNodeStyleSet##type, node, YGEdgeTop); \ + RCT_SET_YGVALUE(metaProps[META_PROP_BOTTOM], YGNodeStyleSet##type, node, YGEdgeBottom); \ + RCT_SET_YGVALUE(metaProps[META_PROP_HORIZONTAL], YGNodeStyleSet##type, node, YGEdgeHorizontal); \ + RCT_SET_YGVALUE(metaProps[META_PROP_VERTICAL], YGNodeStyleSet##type, node, YGEdgeVertical); \ + RCT_SET_YGVALUE(metaProps[META_PROP_ALL], YGNodeStyleSet##type, node, YGEdgeAll); \ } DEFINE_PROCESS_META_PROPS(Padding); DEFINE_PROCESS_META_PROPS(Margin); -DEFINE_PROCESS_META_PROPS(Border); + +static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT], YGNodeRef node) { + YGNodeStyleSetBorder(node, YGEdgeStart, metaProps[META_PROP_LEFT].value); + YGNodeStyleSetBorder(node, YGEdgeEnd, metaProps[META_PROP_RIGHT].value); + YGNodeStyleSetBorder(node, YGEdgeTop, metaProps[META_PROP_TOP].value); + YGNodeStyleSetBorder(node, YGEdgeBottom, metaProps[META_PROP_BOTTOM].value); + YGNodeStyleSetBorder(node, YGEdgeHorizontal, metaProps[META_PROP_HORIZONTAL].value); + YGNodeStyleSetBorder(node, YGEdgeVertical, metaProps[META_PROP_VERTICAL].value); + YGNodeStyleSetBorder(node, YGEdgeAll, metaProps[META_PROP_ALL].value); +} // The absolute stuff is so that we can take into account our absolute position when rounding in order to // snap to the pixel grid. For example, say you have the following structure: @@ -112,9 +134,9 @@ - (void)applyLayoutNode:(YGNodeRef)node #if RCT_DEBUG // This works around a breaking change in css-layout where setting flexBasis needs to be set explicitly, instead of relying on flex to propagate. // We check for it by seeing if a width/height is provided along with a flexBasis of 0 and the width/height is laid out as 0. - if ((YGNodeStyleGetFlexBasis(node).unit == YGUnitPixel && YGNodeStyleGetFlexBasis(node).value == 0) && + if (YGNodeStyleGetFlexBasis(node).unit == YGUnitPixel && YGNodeStyleGetFlexBasis(node).value == 0 && ((YGNodeStyleGetWidth(node).unit == YGUnitPixel && YGNodeStyleGetWidth(node).value > 0 && YGNodeLayoutGetWidth(node) == 0) || - (YGNodeStyleGetHeight(node).unit == YGUnitPixel && YGNodeStyleGetHeight(node).value > 0 && YGNodeLayoutGetHeight(node) == 0))) { + (YGNodeStyleGetHeight(node).unit == YGUnitPixel && YGNodeStyleGetHeight(node).value > 0 && YGNodeLayoutGetHeight(node) == 0))) { RCTLogError(@"View was rendered with explicitly set width/height but with a 0 flexBasis. (This might be fixed by changing flex: to flexGrow:) View: %@", self); } #endif @@ -274,9 +296,9 @@ - (instancetype)init _frame = CGRectMake(0, 0, YGUndefined, YGUndefined); for (unsigned int ii = 0; ii < META_PROP_COUNT; ii++) { - _paddingMetaProps[ii] = YGUndefined; - _marginMetaProps[ii] = YGUndefined; - _borderMetaProps[ii] = YGUndefined; + _paddingMetaProps[ii] = YGValueUndefined; + _marginMetaProps[ii] = YGValueUndefined; + _borderMetaProps[ii] = YGValueUndefined; } _newView = YES; @@ -417,12 +439,12 @@ - (NSString *)recursiveDescription // Margin #define RCT_MARGIN_PROPERTY(prop, metaProp) \ -- (void)setMargin##prop:(float)value \ +- (void)setMargin##prop:(YGValue)value \ { \ _marginMetaProps[META_PROP_##metaProp] = value; \ _recomputeMargin = YES; \ } \ -- (float)margin##prop \ +- (YGValue)margin##prop \ { \ return _marginMetaProps[META_PROP_##metaProp]; \ } @@ -438,12 +460,12 @@ - (float)margin##prop \ // Padding #define RCT_PADDING_PROPERTY(prop, metaProp) \ -- (void)setPadding##prop:(float)value \ +- (void)setPadding##prop:(YGValue)value \ { \ _paddingMetaProps[META_PROP_##metaProp] = value; \ _recomputePadding = YES; \ } \ -- (float)padding##prop \ +- (YGValue)padding##prop \ { \ return _paddingMetaProps[META_PROP_##metaProp]; \ } @@ -468,15 +490,15 @@ - (UIEdgeInsets)paddingAsInsets // Border -#define RCT_BORDER_PROPERTY(prop, metaProp) \ -- (void)setBorder##prop##Width:(float)value \ -{ \ - _borderMetaProps[META_PROP_##metaProp] = value; \ - _recomputeBorder = YES; \ -} \ -- (float)border##prop##Width \ -{ \ - return _borderMetaProps[META_PROP_##metaProp]; \ +#define RCT_BORDER_PROPERTY(prop, metaProp) \ +- (void)setBorder##prop##Width:(float)value \ +{ \ + _borderMetaProps[META_PROP_##metaProp].value = value; \ + _recomputeBorder = YES; \ +} \ +- (float)border##prop##Width \ +{ \ + return _borderMetaProps[META_PROP_##metaProp].value; \ } RCT_BORDER_PROPERTY(, ALL) @@ -487,16 +509,15 @@ - (float)border##prop##Width \ // Dimensions - #define RCT_DIMENSION_PROPERTY(setProp, getProp, cssProp) \ -- (void)set##setProp:(float)value \ +- (void)set##setProp:(YGValue)value \ { \ - YGNodeStyleSet##cssProp(_cssNode, value); \ + RCT_SET_YGVALUE(value, YGNodeStyleSet##cssProp, _cssNode); \ [self dirtyText]; \ } \ -- (float)getProp \ +- (YGValue)getProp \ { \ - return YGNodeStyleGet##cssProp(_cssNode).value; \ + return YGNodeStyleGet##cssProp(_cssNode); \ } RCT_DIMENSION_PROPERTY(Width, width, Width) @@ -509,14 +530,14 @@ - (float)getProp \ // Position #define RCT_POSITION_PROPERTY(setProp, getProp, edge) \ -- (void)set##setProp:(float)value \ +- (void)set##setProp:(YGValue)value \ { \ - YGNodeStyleSetPosition(_cssNode, edge, value); \ + RCT_SET_YGVALUE(value, YGNodeStyleSetPosition, _cssNode, edge); \ [self dirtyText]; \ } \ -- (float)getProp \ +- (YGValue)getProp \ { \ - return YGNodeStyleGetPosition(_cssNode, edge).value; \ + return YGNodeStyleGetPosition(_cssNode, edge); \ } RCT_POSITION_PROPERTY(Top, top, YGEdgeTop) @@ -540,12 +561,12 @@ static inline void RCTAssignSuggestedDimension(YGNodeRef cssNode, YGDimension di if (amount != UIViewNoIntrinsicMetric) { switch (dimension) { case YGDimensionWidth: - if (isnan(YGNodeStyleGetWidth(cssNode).value)) { + if (YGNodeStyleGetWidth(cssNode).unit == YGUnitUndefined) { YGNodeStyleSetWidth(cssNode, amount); } break; case YGDimensionHeight: - if (isnan(YGNodeStyleGetHeight(cssNode).value)) { + if (YGNodeStyleGetHeight(cssNode).unit == YGUnitUndefined) { YGNodeStyleSetHeight(cssNode, amount); } break; @@ -580,14 +601,14 @@ - (void)setFlex:(float)value YGNodeStyleSetFlex(_cssNode, value); } -- (void)setFlexBasis:(float)value +- (void)setFlexBasis:(YGValue)value { - YGNodeStyleSetFlexBasis(_cssNode, value); + RCT_SET_YGVALUE(value, YGNodeStyleSetFlexBasis, _cssNode); } -- (float)flexBasis +- (YGValue)flexBasis { - return YGNodeStyleGetFlexBasis(_cssNode).value; + return YGNodeStyleGetFlexBasis(_cssNode); } #define RCT_STYLE_PROPERTY(setProp, getProp, cssProp, type) \ diff --git a/React/Views/RCTViewManager.m b/React/Views/RCTViewManager.m index 249cfeca47d110..cc1ac26b16d34e 100644 --- a/React/Views/RCTViewManager.m +++ b/React/Views/RCTViewManager.m @@ -264,18 +264,18 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(__unused NSDictio RCT_EXPORT_SHADOW_PROPERTY(backgroundColor, UIColor) -RCT_EXPORT_SHADOW_PROPERTY(top, float) -RCT_EXPORT_SHADOW_PROPERTY(right, float) -RCT_EXPORT_SHADOW_PROPERTY(bottom, float) -RCT_EXPORT_SHADOW_PROPERTY(left, float); +RCT_EXPORT_SHADOW_PROPERTY(top, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(right, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(bottom, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(left, YGValue); -RCT_EXPORT_SHADOW_PROPERTY(width, float) -RCT_EXPORT_SHADOW_PROPERTY(height, float) +RCT_EXPORT_SHADOW_PROPERTY(width, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(height, YGValue) -RCT_EXPORT_SHADOW_PROPERTY(minWidth, float) -RCT_EXPORT_SHADOW_PROPERTY(maxWidth, float) -RCT_EXPORT_SHADOW_PROPERTY(minHeight, float) -RCT_EXPORT_SHADOW_PROPERTY(maxHeight, float) +RCT_EXPORT_SHADOW_PROPERTY(minWidth, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(maxWidth, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(minHeight, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(maxHeight, YGValue) RCT_EXPORT_SHADOW_PROPERTY(borderTopWidth, float) RCT_EXPORT_SHADOW_PROPERTY(borderRightWidth, float) @@ -283,26 +283,26 @@ - (RCTViewManagerUIBlock)uiBlockToAmendWithShadowViewRegistry:(__unused NSDictio RCT_EXPORT_SHADOW_PROPERTY(borderLeftWidth, float) RCT_EXPORT_SHADOW_PROPERTY(borderWidth, float) -RCT_EXPORT_SHADOW_PROPERTY(marginTop, float) -RCT_EXPORT_SHADOW_PROPERTY(marginRight, float) -RCT_EXPORT_SHADOW_PROPERTY(marginBottom, float) -RCT_EXPORT_SHADOW_PROPERTY(marginLeft, float) -RCT_EXPORT_SHADOW_PROPERTY(marginVertical, float) -RCT_EXPORT_SHADOW_PROPERTY(marginHorizontal, float) -RCT_EXPORT_SHADOW_PROPERTY(margin, float) - -RCT_EXPORT_SHADOW_PROPERTY(paddingTop, float) -RCT_EXPORT_SHADOW_PROPERTY(paddingRight, float) -RCT_EXPORT_SHADOW_PROPERTY(paddingBottom, float) -RCT_EXPORT_SHADOW_PROPERTY(paddingLeft, float) -RCT_EXPORT_SHADOW_PROPERTY(paddingVertical, float) -RCT_EXPORT_SHADOW_PROPERTY(paddingHorizontal, float) -RCT_EXPORT_SHADOW_PROPERTY(padding, float) +RCT_EXPORT_SHADOW_PROPERTY(marginTop, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(marginRight, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(marginBottom, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(marginLeft, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(marginVertical, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(marginHorizontal, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(margin, YGValue) + +RCT_EXPORT_SHADOW_PROPERTY(paddingTop, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(paddingRight, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(paddingBottom, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(paddingLeft, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(paddingVertical, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(paddingHorizontal, YGValue) +RCT_EXPORT_SHADOW_PROPERTY(padding, YGValue) RCT_EXPORT_SHADOW_PROPERTY(flex, float) RCT_EXPORT_SHADOW_PROPERTY(flexGrow, float) RCT_EXPORT_SHADOW_PROPERTY(flexShrink, float) -RCT_EXPORT_SHADOW_PROPERTY(flexBasis, float) +RCT_EXPORT_SHADOW_PROPERTY(flexBasis, YGValue) RCT_EXPORT_SHADOW_PROPERTY(flexDirection, YGFlexDirection) RCT_EXPORT_SHADOW_PROPERTY(flexWrap, YGWrap) RCT_EXPORT_SHADOW_PROPERTY(justifyContent, YGJustify) diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatARTSurfaceViewShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatARTSurfaceViewShadowNode.java index 60833dbff31edf..6b3c72fc2cc5fc 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatARTSurfaceViewShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatARTSurfaceViewShadowNode.java @@ -24,6 +24,8 @@ import com.facebook.react.uimanager.ReactShadowNode; import com.facebook.react.uimanager.UIViewOperationQueue; import com.facebook.react.views.art.ARTVirtualNode; +import com.facebook.yoga.YogaValue; +import com.facebook.yoga.YogaUnit; /* package */ class FlatARTSurfaceViewShadowNode extends FlatShadowNode implements AndroidView, TextureView.SurfaceTextureListener { @@ -104,13 +106,24 @@ public void resetPaddingChanged() { @Override public void setPadding(int spacingType, float padding) { - if (getStylePadding(spacingType) != padding) { + YogaValue current = getStylePadding(spacingType); + if (current.unit != YogaUnit.PIXEL || current.value != padding) { super.setPadding(spacingType, padding); mPaddingChanged = true; markUpdated(); } } + @Override + public void setPaddingPercent(int spacingType, float percent) { + YogaValue current = getStylePadding(spacingType); + if (current.unit != YogaUnit.PERCENT || current.value != percent) { + super.setPadding(spacingType, percent); + mPaddingChanged = true; + markUpdated(); + } + } + @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { mSurface = new Surface(surface); diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatReactModalShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatReactModalShadowNode.java index d4061bfdbfdcc1..138262a6410e67 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/FlatReactModalShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/FlatReactModalShadowNode.java @@ -17,6 +17,8 @@ import android.view.WindowManager; import com.facebook.react.uimanager.ReactShadowNode; +import com.facebook.yoga.YogaValue; +import com.facebook.yoga.YogaUnit; /** * FlatReactModalShadowNode @@ -86,10 +88,21 @@ public void resetPaddingChanged() { @Override public void setPadding(int spacingType, float padding) { - if (getStylePadding(spacingType) != padding) { + YogaValue current = getStylePadding(spacingType); + if (current.unit != YogaUnit.PIXEL || current.value != padding) { super.setPadding(spacingType, padding); mPaddingChanged = true; markUpdated(); } } + + @Override + public void setPaddingPercent(int spacingType, float percent) { + YogaValue current = getStylePadding(spacingType); + if (current.unit != YogaUnit.PERCENT || current.value != percent) { + super.setPadding(spacingType, percent); + mPaddingChanged = true; + markUpdated(); + } + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/flat/NativeViewWrapper.java b/ReactAndroid/src/main/java/com/facebook/react/flat/NativeViewWrapper.java index c692025cb42cad..62099e0d01c081 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/flat/NativeViewWrapper.java +++ b/ReactAndroid/src/main/java/com/facebook/react/flat/NativeViewWrapper.java @@ -18,6 +18,8 @@ import com.facebook.react.uimanager.UIViewOperationQueue; import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.uimanager.ViewManager; +import com.facebook.yoga.YogaValue; +import com.facebook.yoga.YogaUnit; /* package */ final class NativeViewWrapper extends FlatShadowNode implements AndroidView { @@ -101,13 +103,24 @@ public void addChildAt(ReactShadowNode child, int i) { @Override public void setPadding(int spacingType, float padding) { - if (getStylePadding(spacingType) != padding) { + YogaValue current = getStylePadding(spacingType); + if (current.unit != YogaUnit.PIXEL || current.value != padding) { super.setPadding(spacingType, padding); mPaddingChanged = true; markUpdated(); } } + @Override + public void setPaddingPercent(int spacingType, float percent) { + YogaValue current = getStylePadding(spacingType); + if (current.unit != YogaUnit.PERCENT || current.value != percent) { + super.setPadding(spacingType, percent); + mPaddingChanged = true; + markUpdated(); + } + } + @Override public void onCollectExtraUpdates(UIViewOperationQueue uiViewOperationQueue) { if (mReactShadowNode != null && mReactShadowNode.hasUnseenUpdates()) { diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java index e824b8bd602a44..5a40eed69c5bda 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/LayoutShadowNode.java @@ -6,12 +6,17 @@ import java.util.Locale; +import com.facebook.react.bridge.Dynamic; +import com.facebook.react.bridge.ReadableType; + import com.facebook.yoga.YogaAlign; import com.facebook.yoga.YogaConstants; import com.facebook.yoga.YogaFlexDirection; import com.facebook.yoga.YogaJustify; import com.facebook.yoga.YogaOverflow; import com.facebook.yoga.YogaPositionType; +import com.facebook.yoga.YogaValue; +import com.facebook.yoga.YogaUnit; import com.facebook.yoga.YogaWrap; import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.uimanager.annotations.ReactPropGroup; @@ -27,57 +32,107 @@ */ public class LayoutShadowNode extends ReactShadowNode { - @ReactProp(name = ViewProps.WIDTH, defaultFloat = YogaConstants.UNDEFINED) - public void setWidth(float width) { + private static boolean dynamicIsPercent(Dynamic dynamic) { + return dynamic.getType() == ReadableType.String && dynamic.asString().endsWith("%"); + } + + private static float getDynamicAsPercent(Dynamic dynamic) { + final String value = dynamic.asString(); + return Float.parseFloat(value.substring(0, value.length() - 1)); + } + + private static float getDynamicAsFloat(Dynamic dynamic) { + return (float) PixelUtil.toPixelFromDIP(dynamic.asDouble()); + } + + @ReactProp(name = ViewProps.WIDTH) + public void setWidth(Dynamic width) { if (isVirtual()) { return; } - setStyleWidth(YogaConstants.isUndefined(width) ? width : PixelUtil.toPixelFromDIP(width)); + + if (width != null && dynamicIsPercent(width)) { + setStyleWidthPercent(getDynamicAsPercent(width)); + } else { + setStyleWidth(width == null ? YogaConstants.UNDEFINED : getDynamicAsFloat(width)); + } + + width.recycle(); } - @ReactProp(name = ViewProps.MIN_WIDTH, defaultFloat = YogaConstants.UNDEFINED) - public void setMinWidth(float minWidth) { + @ReactProp(name = ViewProps.MIN_WIDTH) + public void setMinWidth(Dynamic minWidth) { if (isVirtual()) { return; } - setStyleMinWidth( - YogaConstants.isUndefined(minWidth) ? minWidth : PixelUtil.toPixelFromDIP(minWidth)); + + if (minWidth != null && dynamicIsPercent(minWidth)) { + setStyleMinWidthPercent(getDynamicAsPercent(minWidth)); + } else { + setStyleMinWidth(minWidth == null ? YogaConstants.UNDEFINED : getDynamicAsFloat(minWidth)); + } + + minWidth.recycle(); } - @ReactProp(name = ViewProps.MAX_WIDTH, defaultFloat = YogaConstants.UNDEFINED) - public void setMaxWidth(float maxWidth) { + @ReactProp(name = ViewProps.MAX_WIDTH) + public void setMaxWidth(Dynamic maxWidth) { if (isVirtual()) { return; } - setStyleMaxWidth( - YogaConstants.isUndefined(maxWidth) ? maxWidth : PixelUtil.toPixelFromDIP(maxWidth)); + + if (maxWidth != null && dynamicIsPercent(maxWidth)) { + setStyleMaxWidthPercent(getDynamicAsPercent(maxWidth)); + } else { + setStyleMaxWidth(maxWidth == null ? YogaConstants.UNDEFINED : getDynamicAsFloat(maxWidth)); + } + + maxWidth.recycle(); } - @ReactProp(name = ViewProps.HEIGHT, defaultFloat = YogaConstants.UNDEFINED) - public void setHeight(float height) { + @ReactProp(name = ViewProps.HEIGHT) + public void setHeight(Dynamic height) { if (isVirtual()) { return; } - setStyleHeight( - YogaConstants.isUndefined(height) ? height : PixelUtil.toPixelFromDIP(height)); + + if (height != null && dynamicIsPercent(height)) { + setStyleHeightPercent(getDynamicAsPercent(height)); + } else { + setStyleHeight(height == null ? YogaConstants.UNDEFINED : getDynamicAsFloat(height)); + } + + height.recycle(); } - @ReactProp(name = ViewProps.MIN_HEIGHT, defaultFloat = YogaConstants.UNDEFINED) - public void setMinHeight(float minHeight) { + @ReactProp(name = ViewProps.MIN_HEIGHT) + public void setMinHeight(Dynamic minHeight) { if (isVirtual()) { return; } - setStyleMinHeight( - YogaConstants.isUndefined(minHeight) ? minHeight : PixelUtil.toPixelFromDIP(minHeight)); + + if (minHeight != null && dynamicIsPercent(minHeight)) { + setStyleMinHeightPercent(getDynamicAsPercent(minHeight)); + } else { + setStyleMinHeight(minHeight == null ? YogaConstants.UNDEFINED : getDynamicAsFloat(minHeight)); + } + + minHeight.recycle(); } - @ReactProp(name = ViewProps.MAX_HEIGHT, defaultFloat = YogaConstants.UNDEFINED) - public void setMaxHeight(float maxHeight) { + @ReactProp(name = ViewProps.MAX_HEIGHT) + public void setMaxHeight(Dynamic maxHeight) { if (isVirtual()) { return; } - setStyleMaxHeight( - YogaConstants.isUndefined(maxHeight) ? maxHeight : PixelUtil.toPixelFromDIP(maxHeight)); + + if (maxHeight != null && dynamicIsPercent(maxHeight)) { + setStyleMaxHeightPercent(getDynamicAsPercent(maxHeight)); + } else { + setStyleMaxHeight(maxHeight == null ? YogaConstants.UNDEFINED : getDynamicAsFloat(maxHeight)); + } + + maxHeight.recycle(); } @ReactProp(name = ViewProps.FLEX, defaultFloat = 0f) @@ -104,12 +159,19 @@ public void setFlexShrink(float flexShrink) { super.setFlexShrink(flexShrink); } - @ReactProp(name = ViewProps.FLEX_BASIS, defaultFloat = 0f) - public void setFlexBasis(float flexBasis) { + @ReactProp(name = ViewProps.FLEX_BASIS) + public void setFlexBasis(Dynamic flexBasis) { if (isVirtual()) { return; } - super.setFlexBasis(flexBasis); + + if (flexBasis != null && dynamicIsPercent(flexBasis)) { + setFlexBasisPercent(getDynamicAsPercent(flexBasis)); + } else { + setFlexBasis(flexBasis == null ? 0 : getDynamicAsFloat(flexBasis)); + } + + flexBasis.recycle(); } @ReactProp(name = ViewProps.ASPECT_RATIO, defaultFloat = YogaConstants.UNDEFINED) @@ -186,12 +248,21 @@ public void setOverflow(@Nullable String overflow) { ViewProps.MARGIN_RIGHT, ViewProps.MARGIN_TOP, ViewProps.MARGIN_BOTTOM, - }, defaultFloat = YogaConstants.UNDEFINED) - public void setMargins(int index, float margin) { + }) + public void setMargins(int index, Dynamic margin) { if (isVirtual()) { return; } - setMargin(ViewProps.PADDING_MARGIN_SPACING_TYPES[index], PixelUtil.toPixelFromDIP(margin)); + + if (margin != null && dynamicIsPercent(margin)) { + setMarginPercent(ViewProps.PADDING_MARGIN_SPACING_TYPES[index], getDynamicAsPercent(margin)); + } else { + setMargin( + ViewProps.PADDING_MARGIN_SPACING_TYPES[index], + margin == null ? YogaConstants.UNDEFINED : getDynamicAsFloat(margin)); + } + + margin.recycle(); } @ReactPropGroup(names = { @@ -202,14 +273,22 @@ public void setMargins(int index, float margin) { ViewProps.PADDING_RIGHT, ViewProps.PADDING_TOP, ViewProps.PADDING_BOTTOM, - }, defaultFloat = YogaConstants.UNDEFINED) - public void setPaddings(int index, float padding) { + }) + public void setPaddings(int index, Dynamic padding) { if (isVirtual()) { return; } - setPadding( - ViewProps.PADDING_MARGIN_SPACING_TYPES[index], - YogaConstants.isUndefined(padding) ? padding : PixelUtil.toPixelFromDIP(padding)); + + if (padding != null && dynamicIsPercent(padding)) { + setPaddingPercent( + ViewProps.PADDING_MARGIN_SPACING_TYPES[index], getDynamicAsPercent(padding)); + } else { + setPadding( + ViewProps.PADDING_MARGIN_SPACING_TYPES[index], + padding == null ? YogaConstants.UNDEFINED : getDynamicAsFloat(padding)); + } + + padding.recycle(); } @ReactPropGroup(names = { @@ -231,14 +310,21 @@ public void setBorderWidths(int index, float borderWidth) { ViewProps.RIGHT, ViewProps.TOP, ViewProps.BOTTOM, - }, defaultFloat = YogaConstants.UNDEFINED) - public void setPositionValues(int index, float position) { + }) + public void setPositionValues(int index, Dynamic position) { if (isVirtual()) { return; } - setPosition( - ViewProps.POSITION_SPACING_TYPES[index], - YogaConstants.isUndefined(position) ? position : PixelUtil.toPixelFromDIP(position)); + + if (position != null && dynamicIsPercent(position)) { + setPositionPercent(ViewProps.POSITION_SPACING_TYPES[index], getDynamicAsPercent(position)); + } else { + setPosition( + ViewProps.POSITION_SPACING_TYPES[index], + position == null ? YogaConstants.UNDEFINED : getDynamicAsFloat(position)); + } + + position.recycle(); } @ReactProp(name = ViewProps.POSITION) diff --git a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java index 314b0f263e4865..838c3f4905cd2d 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/uimanager/ReactShadowNode.java @@ -11,6 +11,7 @@ import javax.annotation.Nullable; +import java.util.Arrays; import java.util.ArrayList; import com.facebook.yoga.YogaAlign; @@ -23,6 +24,7 @@ import com.facebook.yoga.YogaNode; import com.facebook.yoga.YogaOverflow; import com.facebook.yoga.YogaPositionType; +import com.facebook.yoga.YogaValue; import com.facebook.yoga.YogaWrap; import com.facebook.infer.annotation.Assertions; import com.facebook.react.uimanager.annotations.ReactPropertyHolder; @@ -72,7 +74,8 @@ public class ReactShadowNode { private float mAbsoluteRight; private float mAbsoluteBottom; private final Spacing mDefaultPadding = new Spacing(0); - private final Spacing mPadding = new Spacing(YogaConstants.UNDEFINED); + private final float[] mPadding = new float[Spacing.ALL + 1]; + private final boolean[] mPaddingIsPercent = new boolean[Spacing.ALL + 1]; private final YogaNode mYogaNode; public ReactShadowNode() { @@ -82,6 +85,7 @@ public ReactShadowNode() { node = new YogaNode(); } mYogaNode = node; + Arrays.fill(mPadding, YogaConstants.UNDEFINED); } else { mYogaNode = null; } @@ -514,38 +518,62 @@ public void setLayoutDirection(YogaDirection direction) { mYogaNode.setDirection(direction); } - public final float getStyleWidth() { - return mYogaNode.getWidth().value; + public final YogaValue getStyleWidth() { + return mYogaNode.getWidth(); } public void setStyleWidth(float widthPx) { mYogaNode.setWidth(widthPx); } + public void setStyleWidthPercent(float percent) { + mYogaNode.setWidthPercent(percent); + } + public void setStyleMinWidth(float widthPx) { mYogaNode.setMinWidth(widthPx); } + public void setStyleMinWidthPercent(float percent) { + mYogaNode.setMinWidthPercent(percent); + } + public void setStyleMaxWidth(float widthPx) { mYogaNode.setMaxWidth(widthPx); } - public final float getStyleHeight() { - return mYogaNode.getHeight().value; + public void setStyleMaxWidthPercent(float percent) { + mYogaNode.setMaxWidthPercent(percent); + } + + public final YogaValue getStyleHeight() { + return mYogaNode.getHeight(); } public void setStyleHeight(float heightPx) { mYogaNode.setHeight(heightPx); } + public void setStyleHeightPercent(float percent) { + mYogaNode.setHeightPercent(percent); + } + public void setStyleMinHeight(float widthPx) { mYogaNode.setMinHeight(widthPx); } + public void setStyleMinHeightPercent(float percent) { + mYogaNode.setMinHeightPercent(percent); + } + public void setStyleMaxHeight(float widthPx) { mYogaNode.setMaxHeight(widthPx); } + public void setStyleMaxHeightPercent(float percent) { + mYogaNode.setMaxHeightPercent(percent); + } + public void setFlex(float flex) { mYogaNode.setFlex(flex); } @@ -562,6 +590,10 @@ public void setFlexBasis(float flexBasis) { mYogaNode.setFlexBasis(flexBasis); } + public void setFlexBasisPercent(float percent) { + mYogaNode.setFlexBasisPercent(percent); + } + public void setStyleAspectRatio(float aspectRatio) { mYogaNode.setAspectRatio(aspectRatio); } @@ -594,12 +626,16 @@ public void setMargin(int spacingType, float margin) { mYogaNode.setMargin(YogaEdge.fromInt(spacingType), margin); } + public void setMarginPercent(int spacingType, float percent) { + mYogaNode.setMarginPercent(YogaEdge.fromInt(spacingType), percent); + } + public final float getPadding(int spacingType) { return mYogaNode.getLayoutPadding(YogaEdge.fromInt(spacingType)); } - public final float getStylePadding(int spacingType) { - return mYogaNode.getPadding(YogaEdge.fromInt(spacingType)).value; + public final YogaValue getStylePadding(int spacingType) { + return mYogaNode.getPadding(YogaEdge.fromInt(spacingType)); } public void setDefaultPadding(int spacingType, float padding) { @@ -608,7 +644,14 @@ public void setDefaultPadding(int spacingType, float padding) { } public void setPadding(int spacingType, float padding) { - mPadding.set(spacingType, padding); + mPadding[spacingType] = padding; + mPaddingIsPercent[spacingType] = false; + updatePadding(); + } + + public void setPaddingPercent(int spacingType, float percent) { + mPadding[spacingType] = percent; + mPaddingIsPercent[spacingType] = !YogaConstants.isUndefined(percent); updatePadding(); } @@ -618,28 +661,31 @@ private void updatePadding() { spacingType == Spacing.RIGHT || spacingType == Spacing.START || spacingType == Spacing.END) { - if (YogaConstants.isUndefined(mPadding.getRaw(spacingType)) && - YogaConstants.isUndefined(mPadding.getRaw(Spacing.HORIZONTAL)) && - YogaConstants.isUndefined(mPadding.getRaw(Spacing.ALL))) { + if (YogaConstants.isUndefined(mPadding[spacingType]) && + YogaConstants.isUndefined(mPadding[Spacing.HORIZONTAL]) && + YogaConstants.isUndefined(mPadding[Spacing.ALL])) { mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mDefaultPadding.getRaw(spacingType)); - } else { - mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mPadding.getRaw(spacingType)); + continue; } } else if (spacingType == Spacing.TOP || spacingType == Spacing.BOTTOM) { - if (YogaConstants.isUndefined(mPadding.getRaw(spacingType)) && - YogaConstants.isUndefined(mPadding.getRaw(Spacing.VERTICAL)) && - YogaConstants.isUndefined(mPadding.getRaw(Spacing.ALL))) { + if (YogaConstants.isUndefined(mPadding[spacingType]) && + YogaConstants.isUndefined(mPadding[Spacing.VERTICAL]) && + YogaConstants.isUndefined(mPadding[Spacing.ALL])) { mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mDefaultPadding.getRaw(spacingType)); - } else { - mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mPadding.getRaw(spacingType)); + continue; } } else { - if (YogaConstants.isUndefined(mPadding.getRaw(spacingType))) { + if (YogaConstants.isUndefined(mPadding[spacingType])) { mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mDefaultPadding.getRaw(spacingType)); - } else { - mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mPadding.getRaw(spacingType)); + continue; } } + + if (mPaddingIsPercent[spacingType]) { + mYogaNode.setPaddingPercent(YogaEdge.fromInt(spacingType), mPadding[spacingType]); + } else { + mYogaNode.setPadding(YogaEdge.fromInt(spacingType), mPadding[spacingType]); + } } } @@ -651,6 +697,10 @@ public void setPosition(int spacingType, float position) { mYogaNode.setPosition(YogaEdge.fromInt(spacingType), position); } + public void setPositionPercent(int spacingType, float percent) { + mYogaNode.setPositionPercent(YogaEdge.fromInt(spacingType), percent); + } + public void setPositionType(YogaPositionType positionType) { mYogaNode.setPositionType(positionType); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageShadowNode.java b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageShadowNode.java index ed251529ed370e..786f3bfbac16f7 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageShadowNode.java +++ b/ReactAndroid/src/main/java/com/facebook/react/views/text/frescosupport/FrescoBasedReactTextInlineImageShadowNode.java @@ -20,7 +20,10 @@ import com.facebook.common.util.UriUtil; import com.facebook.yoga.YogaConstants; import com.facebook.drawee.controller.AbstractDraweeControllerBuilder; +import com.facebook.react.bridge.Dynamic; +import com.facebook.react.bridge.JSApplicationIllegalArgumentException; import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.bridge.ReadableType; import com.facebook.react.uimanager.ViewProps; import com.facebook.react.uimanager.annotations.ReactProp; import com.facebook.react.views.text.ReactTextInlineImageShadowNode; @@ -74,13 +77,23 @@ public void setSource(@Nullable ReadableArray sources) { * Besides width/height, all other layout props on inline images are ignored */ @Override - public void setWidth(float width) { - mWidth = width; + public void setWidth(Dynamic width) { + if (width.getType() == ReadableType.Number) { + mWidth = (float) width.asDouble(); + } else { + throw new JSApplicationIllegalArgumentException( + "Inline images must not have percentage based width"); + } } @Override - public void setHeight(float height) { - mHeight = height; + public void setHeight(Dynamic height) { + if (height.getType() == ReadableType.Number) { + mHeight = (float) height.asDouble(); + } else { + throw new JSApplicationIllegalArgumentException( + "Inline images must not have percentage based height"); + } } public @Nullable Uri getUri() { diff --git a/ReactAndroid/src/main/java/com/facebook/yoga/YogaValue.java b/ReactAndroid/src/main/java/com/facebook/yoga/YogaValue.java index c2d257fc26e6f5..1110eded20b700 100644 --- a/ReactAndroid/src/main/java/com/facebook/yoga/YogaValue.java +++ b/ReactAndroid/src/main/java/com/facebook/yoga/YogaValue.java @@ -19,7 +19,7 @@ public class YogaValue { public final float value; public final YogaUnit unit; - YogaValue(float value, YogaUnit unit) { + public YogaValue(float value, YogaUnit unit) { this.value = value; this.unit = unit; } diff --git a/ReactCommon/yoga/yoga/Yoga.c b/ReactCommon/yoga/yoga/Yoga.c index 0e7bec477875e0..598f273cf6996d 100644 --- a/ReactCommon/yoga/yoga/Yoga.c +++ b/ReactCommon/yoga/yoga/Yoga.c @@ -181,8 +181,6 @@ YGCalloc gYGCalloc = &calloc; YGRealloc gYGRealloc = &realloc; YGFree gYGFree = &free; -static YGValue YGValueUndefined = YG_UNDEFINED_VALUES; - static YGValue YGValueZero = {.value = 0, .unit = YGUnitPixel}; #ifdef ANDROID diff --git a/ReactCommon/yoga/yoga/Yoga.h b/ReactCommon/yoga/yoga/Yoga.h index 34a90f5772ee59..cb21910dc46d21 100644 --- a/ReactCommon/yoga/yoga/Yoga.h +++ b/ReactCommon/yoga/yoga/Yoga.h @@ -43,6 +43,8 @@ typedef struct YGValue { YGUnit unit; } YGValue; +static const YGValue YGValueUndefined = { YGUndefined, YGUnitUndefined }; + typedef struct YGNode *YGNodeRef; typedef YGSize (*YGMeasureFunc)(YGNodeRef node, float width,