// Copyright 2004-present Facebook. All Rights Reserved.
package com.facebook.react.uimanager;
import javax.annotation.Nullable;
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.YogaDisplay;
import com.facebook.yoga.YogaFlexDirection;
import com.facebook.yoga.YogaJustify;
import com.facebook.yoga.YogaOverflow;
import com.facebook.yoga.YogaPositionType;
import com.facebook.yoga.YogaUnit;
import com.facebook.yoga.YogaWrap;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.annotations.ReactPropGroup;
/**
* Supply setters for base view layout properties such as width, height, flex properties,
* borders, etc.
*
* Checking for isVirtual everywhere is a hack to get around the fact that some virtual nodes still
* have layout properties set on them in JS: for example, a component that returns a <Text> may
* or may not be embedded in a parent text. There are better solutions that should probably be
* explored, namely using the VirtualText class in JS and setting the correct set of validAttributes
*/
public class LayoutShadowNode extends ReactShadowNode {
/**
* A Mutable version of com.facebook.yoga.YogaValue
*/
private static class MutableYogaValue {
float value;
YogaUnit unit;
void setFromDynamic(Dynamic dynamic) {
if (dynamic.isNull()) {
unit = YogaUnit.UNDEFINED;
value = YogaConstants.UNDEFINED;
} else if (dynamic.getType() == ReadableType.String) {
final String s = dynamic.asString();
if (s.equals("auto")) {
unit = YogaUnit.AUTO;
value = YogaConstants.UNDEFINED;
} else if (s.endsWith("%")) {
unit = YogaUnit.PERCENT;
value = Float.parseFloat(s.substring(0, s.length() - 1));
} else {
throw new IllegalArgumentException("Unknown value: " + s);
}
} else {
unit = YogaUnit.POINT;
value = PixelUtil.toPixelFromDIP(dynamic.asDouble());
}
}
}
private final MutableYogaValue mTempYogaValue = new MutableYogaValue();
@ReactProp(name = ViewProps.WIDTH)
public void setWidth(Dynamic width) {
if (isVirtual()) {
return;
}
mTempYogaValue.setFromDynamic(width);
switch (mTempYogaValue.unit) {
case POINT:
case UNDEFINED:
setStyleWidth(mTempYogaValue.value);
break;
case AUTO:
setStyleWidthAuto();
break;
case PERCENT:
setStyleWidthPercent(mTempYogaValue.value);
break;
}
width.recycle();
}
@ReactProp(name = ViewProps.MIN_WIDTH)
public void setMinWidth(Dynamic minWidth) {
if (isVirtual()) {
return;
}
mTempYogaValue.setFromDynamic(minWidth);
switch (mTempYogaValue.unit) {
case POINT:
case UNDEFINED:
setStyleMinWidth(mTempYogaValue.value);
break;
case PERCENT:
setStyleMinWidthPercent(mTempYogaValue.value);
break;
}
minWidth.recycle();
}
@ReactProp(name = ViewProps.MAX_WIDTH)
public void setMaxWidth(Dynamic maxWidth) {
if (isVirtual()) {
return;
}
mTempYogaValue.setFromDynamic(maxWidth);
switch (mTempYogaValue.unit) {
case POINT:
case UNDEFINED:
setStyleMaxWidth(mTempYogaValue.value);
break;
case PERCENT:
setStyleMaxWidthPercent(mTempYogaValue.value);
break;
}
maxWidth.recycle();
}
@ReactProp(name = ViewProps.HEIGHT)
public void setHeight(Dynamic height) {
if (isVirtual()) {
return;
}
mTempYogaValue.setFromDynamic(height);
switch (mTempYogaValue.unit) {
case POINT:
case UNDEFINED:
setStyleHeight(mTempYogaValue.value);
break;
case AUTO:
setStyleHeightAuto();
break;
case PERCENT:
setStyleHeightPercent(mTempYogaValue.value);
break;
}
height.recycle();
}
@ReactProp(name = ViewProps.MIN_HEIGHT)
public void setMinHeight(Dynamic minHeight) {
if (isVirtual()) {
return;
}
mTempYogaValue.setFromDynamic(minHeight);
switch (mTempYogaValue.unit) {
case POINT:
case UNDEFINED:
setStyleMinHeight(mTempYogaValue.value);
break;
case PERCENT:
setStyleMinHeightPercent(mTempYogaValue.value);
break;
}
minHeight.recycle();
}
@ReactProp(name = ViewProps.MAX_HEIGHT)
public void setMaxHeight(Dynamic maxHeight) {
if (isVirtual()) {
return;
}
mTempYogaValue.setFromDynamic(maxHeight);
switch (mTempYogaValue.unit) {
case POINT:
case UNDEFINED:
setStyleMaxHeight(mTempYogaValue.value);
break;
case PERCENT:
setStyleMaxHeightPercent(mTempYogaValue.value);
break;
}
maxHeight.recycle();
}
@ReactProp(name = ViewProps.FLEX, defaultFloat = 0f)
public void setFlex(float flex) {
if (isVirtual()) {
return;
}
super.setFlex(flex);
}
@ReactProp(name = ViewProps.FLEX_GROW, defaultFloat = 0f)
public void setFlexGrow(float flexGrow) {
if (isVirtual()) {
return;
}
super.setFlexGrow(flexGrow);
}
@ReactProp(name = ViewProps.FLEX_SHRINK, defaultFloat = 0f)
public void setFlexShrink(float flexShrink) {
if (isVirtual()) {
return;
}
super.setFlexShrink(flexShrink);
}
@ReactProp(name = ViewProps.FLEX_BASIS)
public void setFlexBasis(Dynamic flexBasis) {
if (isVirtual()) {
return;
}
mTempYogaValue.setFromDynamic(flexBasis);
switch (mTempYogaValue.unit) {
case POINT:
case UNDEFINED:
setFlexBasis(mTempYogaValue.value);
break;
case AUTO:
setFlexBasisAuto();
break;
case PERCENT:
setFlexBasisPercent(mTempYogaValue.value);
break;
}
flexBasis.recycle();
}
@ReactProp(name = ViewProps.ASPECT_RATIO, defaultFloat = YogaConstants.UNDEFINED)
public void setAspectRatio(float aspectRatio) {
setStyleAspectRatio(aspectRatio);
}
@ReactProp(name = ViewProps.FLEX_DIRECTION)
public void setFlexDirection(@Nullable String flexDirection) {
if (isVirtual()) {
return;
}
setFlexDirection(
flexDirection == null ? YogaFlexDirection.COLUMN : YogaFlexDirection.valueOf(
flexDirection.toUpperCase(Locale.US).replace("-", "_")));
}
@ReactProp(name = ViewProps.FLEX_WRAP)
public void setFlexWrap(@Nullable String flexWrap) {
if (isVirtual()) {
return;
}
if (flexWrap == null || flexWrap.equals("nowrap")) {
setFlexWrap(YogaWrap.NO_WRAP);
} else if (flexWrap.equals("wrap")) {
setFlexWrap(YogaWrap.WRAP);
} else {
throw new IllegalArgumentException("Unknown flexWrap value: " + flexWrap);
}
}
@ReactProp(name = ViewProps.ALIGN_SELF)
public void setAlignSelf(@Nullable String alignSelf) {
if (isVirtual()) {
return;
}
setAlignSelf(alignSelf == null ? YogaAlign.AUTO : YogaAlign.valueOf(
alignSelf.toUpperCase(Locale.US).replace("-", "_")));
}
@ReactProp(name = ViewProps.ALIGN_ITEMS)
public void setAlignItems(@Nullable String alignItems) {
if (isVirtual()) {
return;
}
setAlignItems(
alignItems == null ? YogaAlign.STRETCH : YogaAlign.valueOf(
alignItems.toUpperCase(Locale.US).replace("-", "_")));
}
@ReactProp(name = ViewProps.ALIGN_CONTENT)
public void setAlignContent(@Nullable String alignContent) {
if (isVirtual()) {
return;
}
setAlignContent(
alignContent == null ? YogaAlign.FLEX_START : YogaAlign.valueOf(
alignContent.toUpperCase(Locale.US).replace("-", "_")));
}
@ReactProp(name = ViewProps.JUSTIFY_CONTENT)
public void setJustifyContent(@Nullable String justifyContent) {
if (isVirtual()) {
return;
}
setJustifyContent(justifyContent == null ? YogaJustify.FLEX_START : YogaJustify.valueOf(
justifyContent.toUpperCase(Locale.US).replace("-", "_")));
}
@ReactProp(name = ViewProps.OVERFLOW)
public void setOverflow(@Nullable String overflow) {
if (isVirtual()) {
return;
}
setOverflow(overflow == null ? YogaOverflow.VISIBLE : YogaOverflow.valueOf(
overflow.toUpperCase(Locale.US).replace("-", "_")));
}
@ReactProp(name = ViewProps.DISPLAY)
public void setDisplay(@Nullable String display) {
if (isVirtual()) {
return;
}
setDisplay(display == null ? YogaDisplay.FLEX : YogaDisplay.valueOf(
display.toUpperCase(Locale.US).replace("-", "_")));
}
@ReactPropGroup(names = {
ViewProps.MARGIN,
ViewProps.MARGIN_VERTICAL,
ViewProps.MARGIN_HORIZONTAL,
ViewProps.MARGIN_LEFT,
ViewProps.MARGIN_RIGHT,
ViewProps.MARGIN_TOP,
ViewProps.MARGIN_BOTTOM,
})
public void setMargins(int index, Dynamic margin) {
if (isVirtual()) {
return;
}
mTempYogaValue.setFromDynamic(margin);
switch (mTempYogaValue.unit) {
case POINT:
case UNDEFINED:
setMargin(ViewProps.PADDING_MARGIN_SPACING_TYPES[index], mTempYogaValue.value);
break;
case AUTO:
setMarginAuto(ViewProps.PADDING_MARGIN_SPACING_TYPES[index]);
break;
case PERCENT:
setMarginPercent(ViewProps.PADDING_MARGIN_SPACING_TYPES[index], mTempYogaValue.value);
break;
}
margin.recycle();
}
@ReactPropGroup(names = {
ViewProps.PADDING,
ViewProps.PADDING_VERTICAL,
ViewProps.PADDING_HORIZONTAL,
ViewProps.PADDING_LEFT,
ViewProps.PADDING_RIGHT,
ViewProps.PADDING_TOP,
ViewProps.PADDING_BOTTOM,
})
public void setPaddings(int index, Dynamic padding) {
if (isVirtual()) {
return;
}
mTempYogaValue.setFromDynamic(padding);
switch (mTempYogaValue.unit) {
case POINT:
case UNDEFINED:
setPadding(ViewProps.PADDING_MARGIN_SPACING_TYPES[index], mTempYogaValue.value);
break;
case PERCENT:
setPaddingPercent(ViewProps.PADDING_MARGIN_SPACING_TYPES[index], mTempYogaValue.value);
break;
}
padding.recycle();
}
@ReactPropGroup(names = {
ViewProps.BORDER_WIDTH,
ViewProps.BORDER_LEFT_WIDTH,
ViewProps.BORDER_RIGHT_WIDTH,
ViewProps.BORDER_TOP_WIDTH,
ViewProps.BORDER_BOTTOM_WIDTH,
}, defaultFloat = YogaConstants.UNDEFINED)
public void setBorderWidths(int index, float borderWidth) {
if (isVirtual()) {
return;
}
setBorder(ViewProps.BORDER_SPACING_TYPES[index], PixelUtil.toPixelFromDIP(borderWidth));
}
@ReactPropGroup(names = {
ViewProps.LEFT,
ViewProps.RIGHT,
ViewProps.TOP,
ViewProps.BOTTOM,
})
public void setPositionValues(int index, Dynamic position) {
if (isVirtual()) {
return;
}
mTempYogaValue.setFromDynamic(position);
switch (mTempYogaValue.unit) {
case POINT:
case UNDEFINED:
setPosition(ViewProps.POSITION_SPACING_TYPES[index], mTempYogaValue.value);
break;
case PERCENT:
setPositionPercent(ViewProps.POSITION_SPACING_TYPES[index], mTempYogaValue.value);
break;
}
position.recycle();
}
@ReactProp(name = ViewProps.POSITION)
public void setPosition(@Nullable String position) {
if (isVirtual()) {
return;
}
YogaPositionType positionType = position == null ?
YogaPositionType.RELATIVE : YogaPositionType.valueOf(position.toUpperCase(Locale.US));
setPositionType(positionType);
}
@Override
@ReactProp(name = "onLayout")
public void setShouldNotifyOnLayout(boolean shouldNotifyOnLayout) {
super.setShouldNotifyOnLayout(shouldNotifyOnLayout);
}
}