827 lines
30 KiB
Objective-C
827 lines
30 KiB
Objective-C
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
|
|
#import "RCTShadowView.h"
|
|
|
|
#import "RCTConvert.h"
|
|
#import "RCTLog.h"
|
|
#import "RCTUtils.h"
|
|
#import "UIView+Private.h"
|
|
#import "UIView+React.h"
|
|
#import "RCTI18nUtil.h"
|
|
|
|
typedef void (^RCTActionBlock)(RCTShadowView *shadowViewSelf, id value);
|
|
typedef void (^RCTResetActionBlock)(RCTShadowView *shadowViewSelf);
|
|
|
|
static NSString *const RCTBackgroundColorProp = @"backgroundColor";
|
|
|
|
typedef NS_ENUM(unsigned int, meta_prop_t) {
|
|
META_PROP_LEFT,
|
|
META_PROP_TOP,
|
|
META_PROP_RIGHT,
|
|
META_PROP_BOTTOM,
|
|
META_PROP_START,
|
|
META_PROP_END,
|
|
META_PROP_HORIZONTAL,
|
|
META_PROP_VERTICAL,
|
|
META_PROP_ALL,
|
|
META_PROP_COUNT,
|
|
};
|
|
|
|
@implementation RCTShadowView
|
|
{
|
|
RCTUpdateLifecycle _propagationLifecycle;
|
|
RCTUpdateLifecycle _textLifecycle;
|
|
NSDictionary *_lastParentProperties;
|
|
NSMutableArray<RCTShadowView *> *_reactSubviews;
|
|
BOOL _recomputePadding;
|
|
BOOL _recomputeMargin;
|
|
BOOL _recomputeBorder;
|
|
BOOL _didUpdateSubviews;
|
|
YGValue _paddingMetaProps[META_PROP_COUNT];
|
|
YGValue _marginMetaProps[META_PROP_COUNT];
|
|
YGValue _borderMetaProps[META_PROP_COUNT];
|
|
}
|
|
|
|
+ (YGConfigRef)yogaConfig
|
|
{
|
|
static YGConfigRef yogaConfig;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
yogaConfig = YGConfigNew();
|
|
// Turnig off pixel rounding.
|
|
YGConfigSetPointScaleFactor(yogaConfig, 0.0);
|
|
YGConfigSetUseLegacyStretchBehaviour(yogaConfig, true);
|
|
});
|
|
return yogaConfig;
|
|
}
|
|
|
|
@synthesize reactTag = _reactTag;
|
|
|
|
// YogaNode API
|
|
|
|
static void RCTPrint(YGNodeRef node)
|
|
{
|
|
RCTShadowView *shadowView = (__bridge RCTShadowView *)YGNodeGetContext(node);
|
|
printf("%s(%lld), ", shadowView.viewName.UTF8String, (long long)shadowView.reactTag.integerValue);
|
|
}
|
|
|
|
#define RCT_SET_YGVALUE(ygvalue, setter, ...) \
|
|
switch (ygvalue.unit) { \
|
|
case YGUnitAuto: \
|
|
case YGUnitUndefined: \
|
|
setter(__VA_ARGS__, YGUndefined); \
|
|
break; \
|
|
case YGUnitPoint: \
|
|
setter(__VA_ARGS__, ygvalue.value); \
|
|
break; \
|
|
case YGUnitPercent: \
|
|
setter##Percent(__VA_ARGS__, ygvalue.value); \
|
|
break; \
|
|
}
|
|
|
|
#define RCT_SET_YGVALUE_AUTO(ygvalue, setter, ...) \
|
|
switch (ygvalue.unit) { \
|
|
case YGUnitAuto: \
|
|
setter##Auto(__VA_ARGS__); \
|
|
break; \
|
|
case YGUnitUndefined: \
|
|
setter(__VA_ARGS__, YGUndefined); \
|
|
break; \
|
|
case YGUnitPoint: \
|
|
setter(__VA_ARGS__, ygvalue.value); \
|
|
break; \
|
|
case YGUnitPercent: \
|
|
setter##Percent(__VA_ARGS__, ygvalue.value); \
|
|
break; \
|
|
}
|
|
|
|
static void RCTProcessMetaPropsPadding(const YGValue metaProps[META_PROP_COUNT], YGNodeRef node) {
|
|
if (![[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL]) {
|
|
RCT_SET_YGVALUE(metaProps[META_PROP_START], YGNodeStyleSetPadding, node, YGEdgeStart);
|
|
RCT_SET_YGVALUE(metaProps[META_PROP_END], YGNodeStyleSetPadding, node, YGEdgeEnd);
|
|
RCT_SET_YGVALUE(metaProps[META_PROP_LEFT], YGNodeStyleSetPadding, node, YGEdgeLeft);
|
|
RCT_SET_YGVALUE(metaProps[META_PROP_RIGHT], YGNodeStyleSetPadding, node, YGEdgeRight);
|
|
} else {
|
|
YGValue start = metaProps[META_PROP_START].unit == YGUnitUndefined ? metaProps[META_PROP_LEFT] : metaProps[META_PROP_START];
|
|
YGValue end = metaProps[META_PROP_END].unit == YGUnitUndefined ? metaProps[META_PROP_RIGHT] : metaProps[META_PROP_END];
|
|
RCT_SET_YGVALUE(start, YGNodeStyleSetPadding, node, YGEdgeStart);
|
|
RCT_SET_YGVALUE(end, YGNodeStyleSetPadding, node, YGEdgeEnd);
|
|
}
|
|
RCT_SET_YGVALUE(metaProps[META_PROP_TOP], YGNodeStyleSetPadding, node, YGEdgeTop);
|
|
RCT_SET_YGVALUE(metaProps[META_PROP_BOTTOM], YGNodeStyleSetPadding, node, YGEdgeBottom);
|
|
RCT_SET_YGVALUE(metaProps[META_PROP_HORIZONTAL], YGNodeStyleSetPadding, node, YGEdgeHorizontal);
|
|
RCT_SET_YGVALUE(metaProps[META_PROP_VERTICAL], YGNodeStyleSetPadding, node, YGEdgeVertical);
|
|
RCT_SET_YGVALUE(metaProps[META_PROP_ALL], YGNodeStyleSetPadding, node, YGEdgeAll);
|
|
}
|
|
|
|
static void RCTProcessMetaPropsMargin(const YGValue metaProps[META_PROP_COUNT], YGNodeRef node) {
|
|
if (![[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL]) {
|
|
RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_START], YGNodeStyleSetMargin, node, YGEdgeStart);
|
|
RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_END], YGNodeStyleSetMargin, node, YGEdgeEnd);
|
|
RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_LEFT], YGNodeStyleSetMargin, node, YGEdgeLeft);
|
|
RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_RIGHT], YGNodeStyleSetMargin, node, YGEdgeRight);
|
|
} else {
|
|
YGValue start = metaProps[META_PROP_START].unit == YGUnitUndefined ? metaProps[META_PROP_LEFT] : metaProps[META_PROP_START];
|
|
YGValue end = metaProps[META_PROP_END].unit == YGUnitUndefined ? metaProps[META_PROP_RIGHT] : metaProps[META_PROP_END];
|
|
RCT_SET_YGVALUE_AUTO(start, YGNodeStyleSetMargin, node, YGEdgeStart);
|
|
RCT_SET_YGVALUE_AUTO(end, YGNodeStyleSetMargin, node, YGEdgeEnd);
|
|
}
|
|
RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_TOP], YGNodeStyleSetMargin, node, YGEdgeTop);
|
|
RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_BOTTOM], YGNodeStyleSetMargin, node, YGEdgeBottom);
|
|
RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_HORIZONTAL], YGNodeStyleSetMargin, node, YGEdgeHorizontal);
|
|
RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_VERTICAL], YGNodeStyleSetMargin, node, YGEdgeVertical);
|
|
RCT_SET_YGVALUE_AUTO(metaProps[META_PROP_ALL], YGNodeStyleSetMargin, node, YGEdgeAll);
|
|
}
|
|
|
|
static void RCTProcessMetaPropsBorder(const YGValue metaProps[META_PROP_COUNT], YGNodeRef node) {
|
|
if (![[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL]) {
|
|
YGNodeStyleSetBorder(node, YGEdgeStart, metaProps[META_PROP_START].value);
|
|
YGNodeStyleSetBorder(node, YGEdgeEnd, metaProps[META_PROP_END].value);
|
|
YGNodeStyleSetBorder(node, YGEdgeLeft, metaProps[META_PROP_LEFT].value);
|
|
YGNodeStyleSetBorder(node, YGEdgeRight, metaProps[META_PROP_RIGHT].value);
|
|
} else {
|
|
const float start = YGFloatIsUndefined(metaProps[META_PROP_START].value) ? metaProps[META_PROP_LEFT].value : metaProps[META_PROP_START].value;
|
|
const float end = YGFloatIsUndefined(metaProps[META_PROP_END].value) ? metaProps[META_PROP_RIGHT].value : metaProps[META_PROP_END].value;
|
|
YGNodeStyleSetBorder(node, YGEdgeStart, start);
|
|
YGNodeStyleSetBorder(node, YGEdgeEnd, end);
|
|
}
|
|
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:
|
|
//
|
|
// +--------+---------+--------+
|
|
// | |+-------+| |
|
|
// | || || |
|
|
// | |+-------+| |
|
|
// +--------+---------+--------+
|
|
//
|
|
// Say the screen width is 320 pts so the three big views will get the following x bounds from our layout system:
|
|
// {0, 106.667}, {106.667, 213.333}, {213.333, 320}
|
|
//
|
|
// Assuming screen scale is 2, these numbers must be rounded to the nearest 0.5 to fit the pixel grid:
|
|
// {0, 106.5}, {106.5, 213.5}, {213.5, 320}
|
|
// You'll notice that the three widths are 106.5, 107, 106.5.
|
|
//
|
|
// This is great for the parent views but it gets trickier when we consider rounding for the subview.
|
|
//
|
|
// When we go to round the bounds for the subview in the middle, it's relative bounds are {0, 106.667}
|
|
// which gets rounded to {0, 106.5}. This will cause the subview to be one pixel smaller than it should be.
|
|
// this is why we need to pass in the absolute position in order to do the rounding relative to the screen's
|
|
// grid rather than the view's grid.
|
|
//
|
|
// After passing in the absolutePosition of {106.667, y}, we do the following calculations:
|
|
// absoluteLeft = round(absolutePosition.x + viewPosition.left) = round(106.667 + 0) = 106.5
|
|
// absoluteRight = round(absolutePosition.x + viewPosition.left + viewSize.left) + round(106.667 + 0 + 106.667) = 213.5
|
|
// width = 213.5 - 106.5 = 107
|
|
// You'll notice that this is the same width we calculated for the parent view because we've taken its position into account.
|
|
|
|
- (void)applyLayoutNode:(YGNodeRef)node
|
|
viewsWithNewFrame:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame
|
|
absolutePosition:(CGPoint)absolutePosition
|
|
{
|
|
if (!YGNodeGetHasNewLayout(node)) {
|
|
return;
|
|
}
|
|
|
|
RCTAssert(!YGNodeIsDirty(node), @"Attempt to get layout metrics from dirtied Yoga node.");
|
|
|
|
YGNodeSetHasNewLayout(node, false);
|
|
|
|
if (YGNodeStyleGetDisplay(node) == YGDisplayNone) {
|
|
// If the node is hidden (has `display: none;`), its (and its descendants)
|
|
// layout metrics are invalid and/or dirtied, so we have to stop here.
|
|
return;
|
|
}
|
|
|
|
#if RCT_DEBUG
|
|
// This works around a breaking change in Yoga 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 == YGUnitPoint && YGNodeStyleGetFlexBasis(node).value == 0 &&
|
|
((YGNodeStyleGetWidth(node).unit == YGUnitPoint && YGNodeStyleGetWidth(node).value > 0 && YGNodeLayoutGetWidth(node) == 0) ||
|
|
(YGNodeStyleGetHeight(node).unit == YGUnitPoint && 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
|
|
|
|
CGPoint absoluteTopLeft = {
|
|
absolutePosition.x + YGNodeLayoutGetLeft(node),
|
|
absolutePosition.y + YGNodeLayoutGetTop(node)
|
|
};
|
|
|
|
CGPoint absoluteBottomRight = {
|
|
absolutePosition.x + YGNodeLayoutGetLeft(node) + YGNodeLayoutGetWidth(node),
|
|
absolutePosition.y + YGNodeLayoutGetTop(node) + YGNodeLayoutGetHeight(node)
|
|
};
|
|
|
|
CGRect frame = {{
|
|
RCTRoundPixelValue(YGNodeLayoutGetLeft(node)),
|
|
RCTRoundPixelValue(YGNodeLayoutGetTop(node)),
|
|
}, {
|
|
RCTRoundPixelValue(absoluteBottomRight.x - absoluteTopLeft.x),
|
|
RCTRoundPixelValue(absoluteBottomRight.y - absoluteTopLeft.y)
|
|
}};
|
|
|
|
// Even if `YGNodeLayoutGetDirection` can return `YGDirectionInherit` here, it actually means
|
|
// that Yoga will use LTR layout for the view (even if layout process is not finished yet).
|
|
UIUserInterfaceLayoutDirection updatedLayoutDirection = YGNodeLayoutGetDirection(_yogaNode) == YGDirectionRTL ? UIUserInterfaceLayoutDirectionRightToLeft : UIUserInterfaceLayoutDirectionLeftToRight;
|
|
|
|
if (!CGRectEqualToRect(frame, _frame) || _layoutDirection != updatedLayoutDirection) {
|
|
_frame = frame;
|
|
_layoutDirection = updatedLayoutDirection;
|
|
[viewsWithNewFrame addObject:self];
|
|
}
|
|
|
|
absolutePosition.x += YGNodeLayoutGetLeft(node);
|
|
absolutePosition.y += YGNodeLayoutGetTop(node);
|
|
|
|
[self applyLayoutToChildren:node viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition];
|
|
}
|
|
|
|
- (void)applyLayoutToChildren:(YGNodeRef)node
|
|
viewsWithNewFrame:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame
|
|
absolutePosition:(CGPoint)absolutePosition
|
|
{
|
|
for (unsigned int i = 0; i < YGNodeGetChildCount(node); ++i) {
|
|
RCTShadowView *child = (RCTShadowView *)_reactSubviews[i];
|
|
[child applyLayoutNode:YGNodeGetChild(node, i)
|
|
viewsWithNewFrame:viewsWithNewFrame
|
|
absolutePosition:absolutePosition];
|
|
}
|
|
}
|
|
|
|
- (NSDictionary<NSString *, id> *)processUpdatedProperties:(NSMutableSet<RCTApplierBlock> *)applierBlocks
|
|
parentProperties:(NSDictionary<NSString *, id> *)parentProperties
|
|
{
|
|
// TODO: we always refresh all propagated properties when propagation is
|
|
// dirtied, but really we should track which properties have changed and
|
|
// only update those.
|
|
|
|
if (_didUpdateSubviews) {
|
|
_didUpdateSubviews = NO;
|
|
[self didUpdateReactSubviews];
|
|
[applierBlocks addObject:^(NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
UIView *view = viewRegistry[self->_reactTag];
|
|
[view didUpdateReactSubviews];
|
|
}];
|
|
}
|
|
|
|
if (!_backgroundColor) {
|
|
UIColor *parentBackgroundColor = parentProperties[RCTBackgroundColorProp];
|
|
if (parentBackgroundColor) {
|
|
[applierBlocks addObject:^(NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
UIView *view = viewRegistry[self->_reactTag];
|
|
[view reactSetInheritedBackgroundColor:parentBackgroundColor];
|
|
}];
|
|
}
|
|
} else {
|
|
// Update parent properties for children
|
|
NSMutableDictionary<NSString *, id> *properties = [NSMutableDictionary dictionaryWithDictionary:parentProperties];
|
|
CGFloat alpha = CGColorGetAlpha(_backgroundColor.CGColor);
|
|
if (alpha < 1.0) {
|
|
// If bg is non-opaque, don't propagate further
|
|
properties[RCTBackgroundColorProp] = [UIColor clearColor];
|
|
} else {
|
|
properties[RCTBackgroundColorProp] = _backgroundColor;
|
|
}
|
|
return properties;
|
|
}
|
|
return parentProperties;
|
|
}
|
|
|
|
- (void)collectUpdatedProperties:(NSMutableSet<RCTApplierBlock> *)applierBlocks
|
|
parentProperties:(NSDictionary<NSString *, id> *)parentProperties
|
|
{
|
|
if (_propagationLifecycle == RCTUpdateLifecycleComputed && [parentProperties isEqualToDictionary:_lastParentProperties]) {
|
|
return;
|
|
}
|
|
_propagationLifecycle = RCTUpdateLifecycleComputed;
|
|
_lastParentProperties = parentProperties;
|
|
NSDictionary<NSString *, id> *nextProps = [self processUpdatedProperties:applierBlocks parentProperties:parentProperties];
|
|
for (RCTShadowView *child in _reactSubviews) {
|
|
[child collectUpdatedProperties:applierBlocks parentProperties:nextProps];
|
|
}
|
|
}
|
|
|
|
- (void)collectUpdatedFrames:(NSMutableSet<RCTShadowView *> *)viewsWithNewFrame
|
|
withFrame:(CGRect)frame
|
|
hidden:(BOOL)hidden
|
|
absolutePosition:(CGPoint)absolutePosition
|
|
{
|
|
// This is not the core layout method. It is only used by RCTShadowText to layout
|
|
// nested views.
|
|
|
|
if (_hidden != hidden) {
|
|
// The hidden state has changed. Even if the frame hasn't changed, add
|
|
// this ShadowView to viewsWithNewFrame so the UIManager will process
|
|
// this ShadowView's UIView and update its hidden state.
|
|
_hidden = hidden;
|
|
[viewsWithNewFrame addObject:self];
|
|
}
|
|
|
|
if (!CGRectEqualToRect(frame, _frame)) {
|
|
YGNodeStyleSetPositionType(_yogaNode, YGPositionTypeAbsolute);
|
|
YGNodeStyleSetWidth(_yogaNode, frame.size.width);
|
|
YGNodeStyleSetHeight(_yogaNode, frame.size.height);
|
|
YGNodeStyleSetPosition(_yogaNode, YGEdgeLeft, frame.origin.x);
|
|
YGNodeStyleSetPosition(_yogaNode, YGEdgeTop, frame.origin.y);
|
|
}
|
|
|
|
YGNodeCalculateLayout(_yogaNode, frame.size.width, frame.size.height, YGDirectionInherit);
|
|
[self applyLayoutNode:_yogaNode viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition];
|
|
}
|
|
|
|
- (CGRect)measureLayoutRelativeToAncestor:(RCTShadowView *)ancestor
|
|
{
|
|
CGPoint offset = CGPointZero;
|
|
NSInteger depth = 30; // max depth to search
|
|
RCTShadowView *shadowView = self;
|
|
while (depth && shadowView && shadowView != ancestor) {
|
|
offset.x += shadowView.frame.origin.x;
|
|
offset.y += shadowView.frame.origin.y;
|
|
shadowView = shadowView->_superview;
|
|
depth--;
|
|
}
|
|
if (ancestor != shadowView) {
|
|
return CGRectNull;
|
|
}
|
|
return (CGRect){offset, self.frame.size};
|
|
}
|
|
|
|
- (BOOL)viewIsDescendantOf:(RCTShadowView *)ancestor
|
|
{
|
|
NSInteger depth = 30; // max depth to search
|
|
RCTShadowView *shadowView = self;
|
|
while (depth && shadowView && shadowView != ancestor) {
|
|
shadowView = shadowView->_superview;
|
|
depth--;
|
|
}
|
|
return ancestor == shadowView;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
if ((self = [super init])) {
|
|
_frame = CGRectMake(0, 0, YGUndefined, YGUndefined);
|
|
|
|
for (unsigned int ii = 0; ii < META_PROP_COUNT; ii++) {
|
|
_paddingMetaProps[ii] = YGValueUndefined;
|
|
_marginMetaProps[ii] = YGValueUndefined;
|
|
_borderMetaProps[ii] = YGValueUndefined;
|
|
}
|
|
|
|
_intrinsicContentSize = CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric);
|
|
|
|
_newView = YES;
|
|
_propagationLifecycle = RCTUpdateLifecycleUninitialized;
|
|
_textLifecycle = RCTUpdateLifecycleUninitialized;
|
|
|
|
_reactSubviews = [NSMutableArray array];
|
|
|
|
_yogaNode = YGNodeNewWithConfig([[self class] yogaConfig]);
|
|
YGNodeSetContext(_yogaNode, (__bridge void *)self);
|
|
YGNodeSetPrintFunc(_yogaNode, RCTPrint);
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (BOOL)isReactRootView
|
|
{
|
|
return RCTIsReactRootView(self.reactTag);
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
YGNodeFree(_yogaNode);
|
|
}
|
|
|
|
- (BOOL)canHaveSubviews
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
- (BOOL)isYogaLeafNode
|
|
{
|
|
return NO;
|
|
}
|
|
|
|
- (void)dirtyPropagation
|
|
{
|
|
if (_propagationLifecycle != RCTUpdateLifecycleDirtied) {
|
|
_propagationLifecycle = RCTUpdateLifecycleDirtied;
|
|
[_superview dirtyPropagation];
|
|
}
|
|
}
|
|
|
|
- (BOOL)isPropagationDirty
|
|
{
|
|
return _propagationLifecycle != RCTUpdateLifecycleComputed;
|
|
}
|
|
|
|
- (void)dirtyText
|
|
{
|
|
if (_textLifecycle != RCTUpdateLifecycleDirtied) {
|
|
_textLifecycle = RCTUpdateLifecycleDirtied;
|
|
[_superview dirtyText];
|
|
}
|
|
}
|
|
|
|
- (BOOL)isTextDirty
|
|
{
|
|
return _textLifecycle != RCTUpdateLifecycleComputed;
|
|
}
|
|
|
|
- (void)setTextComputed
|
|
{
|
|
_textLifecycle = RCTUpdateLifecycleComputed;
|
|
}
|
|
|
|
- (void)insertReactSubview:(RCTShadowView *)subview atIndex:(NSInteger)atIndex
|
|
{
|
|
RCTAssert(self.canHaveSubviews, @"Attempt to insert subview inside leaf view.");
|
|
|
|
[_reactSubviews insertObject:subview atIndex:atIndex];
|
|
if (![self isYogaLeafNode]) {
|
|
YGNodeInsertChild(_yogaNode, subview.yogaNode, (uint32_t)atIndex);
|
|
}
|
|
subview->_superview = self;
|
|
_didUpdateSubviews = YES;
|
|
[self dirtyText];
|
|
[self dirtyPropagation];
|
|
}
|
|
|
|
- (void)removeReactSubview:(RCTShadowView *)subview
|
|
{
|
|
[subview dirtyText];
|
|
[subview dirtyPropagation];
|
|
_didUpdateSubviews = YES;
|
|
subview->_superview = nil;
|
|
[_reactSubviews removeObject:subview];
|
|
if (![self isYogaLeafNode]) {
|
|
YGNodeRemoveChild(_yogaNode, subview.yogaNode);
|
|
}
|
|
}
|
|
|
|
- (NSArray<RCTShadowView *> *)reactSubviews
|
|
{
|
|
return _reactSubviews;
|
|
}
|
|
|
|
- (RCTShadowView *)reactSuperview
|
|
{
|
|
return _superview;
|
|
}
|
|
|
|
- (NSNumber *)reactTagAtPoint:(CGPoint)point
|
|
{
|
|
for (RCTShadowView *shadowView in _reactSubviews) {
|
|
if (CGRectContainsPoint(shadowView.frame, point)) {
|
|
CGPoint relativePoint = point;
|
|
CGPoint origin = shadowView.frame.origin;
|
|
relativePoint.x -= origin.x;
|
|
relativePoint.y -= origin.y;
|
|
return [shadowView reactTagAtPoint:relativePoint];
|
|
}
|
|
}
|
|
return self.reactTag;
|
|
}
|
|
|
|
- (NSString *)description
|
|
{
|
|
NSString *description = super.description;
|
|
description = [[description substringToIndex:description.length - 1] stringByAppendingFormat:@"; viewName: %@; reactTag: %@; frame: %@>", self.viewName, self.reactTag, NSStringFromCGRect(self.frame)];
|
|
return description;
|
|
}
|
|
|
|
- (void)addRecursiveDescriptionToString:(NSMutableString *)string atLevel:(NSUInteger)level
|
|
{
|
|
for (NSUInteger i = 0; i < level; i++) {
|
|
[string appendString:@" | "];
|
|
}
|
|
|
|
[string appendString:self.description];
|
|
[string appendString:@"\n"];
|
|
|
|
for (RCTShadowView *subview in _reactSubviews) {
|
|
[subview addRecursiveDescriptionToString:string atLevel:level + 1];
|
|
}
|
|
}
|
|
|
|
- (NSString *)recursiveDescription
|
|
{
|
|
NSMutableString *description = [NSMutableString string];
|
|
[self addRecursiveDescriptionToString:description atLevel:0];
|
|
return description;
|
|
}
|
|
|
|
// Margin
|
|
|
|
#define RCT_MARGIN_PROPERTY(prop, metaProp) \
|
|
- (void)setMargin##prop:(YGValue)value \
|
|
{ \
|
|
_marginMetaProps[META_PROP_##metaProp] = value; \
|
|
_recomputeMargin = YES; \
|
|
} \
|
|
- (YGValue)margin##prop \
|
|
{ \
|
|
return _marginMetaProps[META_PROP_##metaProp]; \
|
|
}
|
|
|
|
RCT_MARGIN_PROPERTY(, ALL)
|
|
RCT_MARGIN_PROPERTY(Vertical, VERTICAL)
|
|
RCT_MARGIN_PROPERTY(Horizontal, HORIZONTAL)
|
|
RCT_MARGIN_PROPERTY(Top, TOP)
|
|
RCT_MARGIN_PROPERTY(Left, LEFT)
|
|
RCT_MARGIN_PROPERTY(Bottom, BOTTOM)
|
|
RCT_MARGIN_PROPERTY(Right, RIGHT)
|
|
RCT_MARGIN_PROPERTY(Start, START)
|
|
RCT_MARGIN_PROPERTY(End, END)
|
|
|
|
// Padding
|
|
|
|
#define RCT_PADDING_PROPERTY(prop, metaProp) \
|
|
- (void)setPadding##prop:(YGValue)value \
|
|
{ \
|
|
_paddingMetaProps[META_PROP_##metaProp] = value; \
|
|
_recomputePadding = YES; \
|
|
} \
|
|
- (YGValue)padding##prop \
|
|
{ \
|
|
return _paddingMetaProps[META_PROP_##metaProp]; \
|
|
}
|
|
|
|
RCT_PADDING_PROPERTY(, ALL)
|
|
RCT_PADDING_PROPERTY(Vertical, VERTICAL)
|
|
RCT_PADDING_PROPERTY(Horizontal, HORIZONTAL)
|
|
RCT_PADDING_PROPERTY(Top, TOP)
|
|
RCT_PADDING_PROPERTY(Left, LEFT)
|
|
RCT_PADDING_PROPERTY(Bottom, BOTTOM)
|
|
RCT_PADDING_PROPERTY(Right, RIGHT)
|
|
RCT_PADDING_PROPERTY(Start, START)
|
|
RCT_PADDING_PROPERTY(End, END)
|
|
|
|
// Border
|
|
|
|
#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)
|
|
RCT_BORDER_PROPERTY(Top, TOP)
|
|
RCT_BORDER_PROPERTY(Left, LEFT)
|
|
RCT_BORDER_PROPERTY(Bottom, BOTTOM)
|
|
RCT_BORDER_PROPERTY(Right, RIGHT)
|
|
RCT_BORDER_PROPERTY(Start, START)
|
|
RCT_BORDER_PROPERTY(End, END)
|
|
|
|
// Dimensions
|
|
#define RCT_DIMENSION_PROPERTY(setProp, getProp, cssProp) \
|
|
- (void)set##setProp:(YGValue)value \
|
|
{ \
|
|
RCT_SET_YGVALUE_AUTO(value, YGNodeStyleSet##cssProp, _yogaNode); \
|
|
[self dirtyText]; \
|
|
} \
|
|
- (YGValue)getProp \
|
|
{ \
|
|
return YGNodeStyleGet##cssProp(_yogaNode); \
|
|
}
|
|
|
|
#define RCT_MIN_MAX_DIMENSION_PROPERTY(setProp, getProp, cssProp) \
|
|
- (void)set##setProp:(YGValue)value \
|
|
{ \
|
|
RCT_SET_YGVALUE(value, YGNodeStyleSet##cssProp, _yogaNode); \
|
|
[self dirtyText]; \
|
|
} \
|
|
- (YGValue)getProp \
|
|
{ \
|
|
return YGNodeStyleGet##cssProp(_yogaNode); \
|
|
}
|
|
|
|
RCT_DIMENSION_PROPERTY(Width, width, Width)
|
|
RCT_DIMENSION_PROPERTY(Height, height, Height)
|
|
RCT_MIN_MAX_DIMENSION_PROPERTY(MinWidth, minWidth, MinWidth)
|
|
RCT_MIN_MAX_DIMENSION_PROPERTY(MinHeight, minHeight, MinHeight)
|
|
RCT_MIN_MAX_DIMENSION_PROPERTY(MaxWidth, maxWidth, MaxWidth)
|
|
RCT_MIN_MAX_DIMENSION_PROPERTY(MaxHeight, maxHeight, MaxHeight)
|
|
|
|
// Position
|
|
|
|
#define RCT_POSITION_PROPERTY(setProp, getProp, edge) \
|
|
- (void)set##setProp:(YGValue)value \
|
|
{ \
|
|
RCT_SET_YGVALUE(value, YGNodeStyleSetPosition, _yogaNode, edge); \
|
|
[self dirtyText]; \
|
|
} \
|
|
- (YGValue)getProp \
|
|
{ \
|
|
return YGNodeStyleGetPosition(_yogaNode, edge); \
|
|
}
|
|
|
|
|
|
RCT_POSITION_PROPERTY(Top, top, YGEdgeTop)
|
|
RCT_POSITION_PROPERTY(Bottom, bottom, YGEdgeBottom)
|
|
RCT_POSITION_PROPERTY(Start, start, YGEdgeStart)
|
|
RCT_POSITION_PROPERTY(End, end, YGEdgeEnd)
|
|
|
|
- (void)setLeft:(YGValue)value
|
|
{
|
|
YGEdge edge = [[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL] ? YGEdgeStart : YGEdgeLeft;
|
|
RCT_SET_YGVALUE(value, YGNodeStyleSetPosition, _yogaNode, edge);
|
|
[self dirtyText];
|
|
}
|
|
- (YGValue)left
|
|
{
|
|
YGEdge edge = [[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL] ? YGEdgeStart : YGEdgeLeft;
|
|
return YGNodeStyleGetPosition(_yogaNode, edge);
|
|
}
|
|
|
|
- (void)setRight:(YGValue)value
|
|
{
|
|
YGEdge edge = [[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL] ? YGEdgeEnd : YGEdgeRight;
|
|
RCT_SET_YGVALUE(value, YGNodeStyleSetPosition, _yogaNode, edge);
|
|
[self dirtyText];
|
|
}
|
|
- (YGValue)right
|
|
{
|
|
YGEdge edge = [[RCTI18nUtil sharedInstance] doLeftAndRightSwapInRTL] ? YGEdgeEnd : YGEdgeRight;
|
|
return YGNodeStyleGetPosition(_yogaNode, edge);
|
|
}
|
|
|
|
// Size
|
|
|
|
- (CGSize)size
|
|
{
|
|
YGValue width = YGNodeStyleGetWidth(_yogaNode);
|
|
YGValue height = YGNodeStyleGetHeight(_yogaNode);
|
|
|
|
return CGSizeMake(
|
|
width.unit == YGUnitPoint ? width.value : NAN,
|
|
height.unit == YGUnitPoint ? height.value : NAN
|
|
);
|
|
}
|
|
|
|
- (void)setSize:(CGSize)size
|
|
{
|
|
YGNodeStyleSetWidth(_yogaNode, size.width);
|
|
YGNodeStyleSetHeight(_yogaNode, size.height);
|
|
}
|
|
|
|
// IntrinsicContentSize
|
|
|
|
static inline YGSize RCTShadowViewMeasure(YGNodeRef node, float width, YGMeasureMode widthMode, float height, YGMeasureMode heightMode)
|
|
{
|
|
RCTShadowView *shadowView = (__bridge RCTShadowView *)YGNodeGetContext(node);
|
|
|
|
CGSize intrinsicContentSize = shadowView->_intrinsicContentSize;
|
|
// Replace `UIViewNoIntrinsicMetric` (which equals `-1`) with zero.
|
|
intrinsicContentSize.width = MAX(0, intrinsicContentSize.width);
|
|
intrinsicContentSize.height = MAX(0, intrinsicContentSize.height);
|
|
|
|
YGSize result;
|
|
|
|
switch (widthMode) {
|
|
case YGMeasureModeUndefined:
|
|
result.width = intrinsicContentSize.width;
|
|
break;
|
|
case YGMeasureModeExactly:
|
|
result.width = width;
|
|
break;
|
|
case YGMeasureModeAtMost:
|
|
result.width = MIN(width, intrinsicContentSize.width);
|
|
break;
|
|
}
|
|
|
|
switch (heightMode) {
|
|
case YGMeasureModeUndefined:
|
|
result.height = intrinsicContentSize.height;
|
|
break;
|
|
case YGMeasureModeExactly:
|
|
result.height = height;
|
|
break;
|
|
case YGMeasureModeAtMost:
|
|
result.height = MIN(height, intrinsicContentSize.height);
|
|
break;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
- (void)setIntrinsicContentSize:(CGSize)intrinsicContentSize
|
|
{
|
|
if (CGSizeEqualToSize(_intrinsicContentSize, intrinsicContentSize)) {
|
|
return;
|
|
}
|
|
|
|
_intrinsicContentSize = intrinsicContentSize;
|
|
|
|
if (CGSizeEqualToSize(_intrinsicContentSize, CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric))) {
|
|
YGNodeSetMeasureFunc(_yogaNode, NULL);
|
|
} else {
|
|
YGNodeSetMeasureFunc(_yogaNode, RCTShadowViewMeasure);
|
|
}
|
|
|
|
YGNodeMarkDirty(_yogaNode);
|
|
}
|
|
|
|
// Local Data
|
|
|
|
- (void)setLocalData:(__unused NSObject *)localData
|
|
{
|
|
// Do nothing by default.
|
|
}
|
|
|
|
// Flex
|
|
|
|
- (void)setFlexBasis:(YGValue)value
|
|
{
|
|
RCT_SET_YGVALUE_AUTO(value, YGNodeStyleSetFlexBasis, _yogaNode);
|
|
}
|
|
|
|
- (YGValue)flexBasis
|
|
{
|
|
return YGNodeStyleGetFlexBasis(_yogaNode);
|
|
}
|
|
|
|
#define RCT_STYLE_PROPERTY(setProp, getProp, cssProp, type) \
|
|
- (void)set##setProp:(type)value \
|
|
{ \
|
|
YGNodeStyleSet##cssProp(_yogaNode, value); \
|
|
} \
|
|
- (type)getProp \
|
|
{ \
|
|
return YGNodeStyleGet##cssProp(_yogaNode); \
|
|
}
|
|
|
|
RCT_STYLE_PROPERTY(Flex, flex, Flex, float)
|
|
RCT_STYLE_PROPERTY(FlexGrow, flexGrow, FlexGrow, float)
|
|
RCT_STYLE_PROPERTY(FlexShrink, flexShrink, FlexShrink, float)
|
|
RCT_STYLE_PROPERTY(FlexDirection, flexDirection, FlexDirection, YGFlexDirection)
|
|
RCT_STYLE_PROPERTY(JustifyContent, justifyContent, JustifyContent, YGJustify)
|
|
RCT_STYLE_PROPERTY(AlignSelf, alignSelf, AlignSelf, YGAlign)
|
|
RCT_STYLE_PROPERTY(AlignItems, alignItems, AlignItems, YGAlign)
|
|
RCT_STYLE_PROPERTY(AlignContent, alignContent, AlignContent, YGAlign)
|
|
RCT_STYLE_PROPERTY(Position, position, PositionType, YGPositionType)
|
|
RCT_STYLE_PROPERTY(FlexWrap, flexWrap, FlexWrap, YGWrap)
|
|
RCT_STYLE_PROPERTY(Overflow, overflow, Overflow, YGOverflow)
|
|
RCT_STYLE_PROPERTY(Display, display, Display, YGDisplay)
|
|
RCT_STYLE_PROPERTY(Direction, direction, Direction, YGDirection)
|
|
RCT_STYLE_PROPERTY(AspectRatio, aspectRatio, AspectRatio, float)
|
|
|
|
- (void)setBackgroundColor:(UIColor *)color
|
|
{
|
|
_backgroundColor = color;
|
|
[self dirtyPropagation];
|
|
}
|
|
|
|
- (void)setZIndex:(NSInteger)zIndex
|
|
{
|
|
_zIndex = zIndex;
|
|
if (_superview) {
|
|
// Changing zIndex means the subview order of the parent needs updating
|
|
_superview->_didUpdateSubviews = YES;
|
|
[_superview dirtyPropagation];
|
|
}
|
|
}
|
|
|
|
- (void)didUpdateReactSubviews
|
|
{
|
|
// Does nothing by default
|
|
}
|
|
|
|
- (void)didSetProps:(__unused NSArray<NSString *> *)changedProps
|
|
{
|
|
if (_recomputePadding) {
|
|
RCTProcessMetaPropsPadding(_paddingMetaProps, _yogaNode);
|
|
}
|
|
if (_recomputeMargin) {
|
|
RCTProcessMetaPropsMargin(_marginMetaProps, _yogaNode);
|
|
}
|
|
if (_recomputeBorder) {
|
|
RCTProcessMetaPropsBorder(_borderMetaProps, _yogaNode);
|
|
}
|
|
_recomputeMargin = NO;
|
|
_recomputePadding = NO;
|
|
_recomputeBorder = NO;
|
|
}
|
|
|
|
@end
|