mirror of
https://github.com/Tencent/Hippy.git
synced 2026-10-29 14:10:47 +08:00
761 lines
30 KiB
Plaintext
761 lines
30 KiB
Plaintext
/*!
|
|
* iOS SDK
|
|
*
|
|
* Tencent is pleased to support the open source community by making
|
|
* Hippy available.
|
|
*
|
|
* Copyright (C) 2019 THL A29 Limited, a Tencent company.
|
|
* All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#import "HippyShadowView.h"
|
|
#import "HippyShadowView+MTTLayout.h"
|
|
#import "HippyConvert.h"
|
|
#import "HippyLog.h"
|
|
#import "HippyUtils.h"
|
|
#import "UIView+Hippy.h"
|
|
#import "UIView+Private.h"
|
|
#import "MTTNode.h"
|
|
#import "HippyI18nUtils.h"
|
|
|
|
static NSString *const HippyBackgroundColorProp = @"backgroundColor";
|
|
|
|
typedef NS_ENUM(unsigned int, meta_prop_t) {
|
|
META_PROP_LEFT,
|
|
META_PROP_TOP,
|
|
META_PROP_RIGHT,
|
|
META_PROP_BOTTOM,
|
|
META_PROP_HORIZONTAL,
|
|
META_PROP_VERTICAL,
|
|
META_PROP_ALL,
|
|
META_PROP_COUNT,
|
|
};
|
|
|
|
@implementation HippyShadowView {
|
|
HippyUpdateLifecycle _propagationLifecycle;
|
|
HippyUpdateLifecycle _textLifecycle;
|
|
NSDictionary *_lastParentProperties;
|
|
NSMutableArray<HippyShadowView *> *_hippySubviews;
|
|
BOOL _recomputePadding;
|
|
BOOL _recomputeMargin;
|
|
BOOL _recomputeBorder;
|
|
BOOL _didUpdateSubviews;
|
|
float _paddingMetaProps[META_PROP_COUNT];
|
|
float _marginMetaProps[META_PROP_COUNT];
|
|
float _borderMetaProps[META_PROP_COUNT];
|
|
}
|
|
|
|
@synthesize hippyTag = _hippyTag;
|
|
@synthesize props = _props;
|
|
@synthesize rootTag = _rootTag;
|
|
@synthesize parent = _parent;
|
|
|
|
|
|
#define DEFINE_PROCESS_META_PROPS(type) \
|
|
static void HippyProcessMetaProps##type(const float metaProps[META_PROP_COUNT], MTTNodeRef node) { \
|
|
if (!isnan(metaProps[META_PROP_LEFT])) { \
|
|
MTTNodeStyleSet##type(node, CSSLeft, metaProps[META_PROP_LEFT]); \
|
|
} else if (!isnan(metaProps[META_PROP_HORIZONTAL])) { \
|
|
MTTNodeStyleSet##type(node, CSSLeft, metaProps[META_PROP_HORIZONTAL]); \
|
|
} else if (!isnan(metaProps[META_PROP_ALL])) { \
|
|
MTTNodeStyleSet##type(node, CSSLeft, metaProps[META_PROP_ALL]); \
|
|
} else { \
|
|
MTTNodeStyleSet##type(node, CSSLeft, 0); \
|
|
} \
|
|
\
|
|
if (!isnan(metaProps[META_PROP_RIGHT])) { \
|
|
MTTNodeStyleSet##type(node, CSSRight, metaProps[META_PROP_RIGHT]); \
|
|
} else if (!isnan(metaProps[META_PROP_HORIZONTAL])) { \
|
|
MTTNodeStyleSet##type(node, CSSRight, metaProps[META_PROP_HORIZONTAL]); \
|
|
} else if (!isnan(metaProps[META_PROP_ALL])) { \
|
|
MTTNodeStyleSet##type(node, CSSRight, metaProps[META_PROP_ALL]); \
|
|
} else { \
|
|
MTTNodeStyleSet##type(node, CSSRight, 0); \
|
|
} \
|
|
\
|
|
if (!isnan(metaProps[META_PROP_TOP])) { \
|
|
MTTNodeStyleSet##type(node, CSSTop, metaProps[META_PROP_TOP]); \
|
|
} else if (!isnan(metaProps[META_PROP_VERTICAL])) { \
|
|
MTTNodeStyleSet##type(node, CSSTop, metaProps[META_PROP_VERTICAL]); \
|
|
} else if (!isnan(metaProps[META_PROP_ALL])) { \
|
|
MTTNodeStyleSet##type(node, CSSTop, metaProps[META_PROP_ALL]); \
|
|
} else { \
|
|
MTTNodeStyleSet##type(node, CSSTop, 0); \
|
|
} \
|
|
\
|
|
if (!isnan(metaProps[META_PROP_BOTTOM])) { \
|
|
MTTNodeStyleSet##type(node, CSSBottom, metaProps[META_PROP_BOTTOM]); \
|
|
} else if (!isnan(metaProps[META_PROP_VERTICAL])) { \
|
|
MTTNodeStyleSet##type(node, CSSBottom, metaProps[META_PROP_VERTICAL]); \
|
|
} else if (!isnan(metaProps[META_PROP_ALL])) { \
|
|
MTTNodeStyleSet##type(node, CSSBottom, metaProps[META_PROP_ALL]); \
|
|
} else { \
|
|
MTTNodeStyleSet##type(node, CSSBottom, 0); \
|
|
} \
|
|
}
|
|
|
|
DEFINE_PROCESS_META_PROPS(Padding);
|
|
DEFINE_PROCESS_META_PROPS(Margin);
|
|
DEFINE_PROCESS_META_PROPS(Border);
|
|
|
|
// 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:(MTTNodeRef)node
|
|
viewsWithNewFrame:(NSMutableSet<HippyShadowView *> *)viewsWithNewFrame
|
|
absolutePosition:(CGPoint)absolutePosition {
|
|
if (!MTTNodeHasNewLayout(node) && !_visibilityChanged) {
|
|
return;
|
|
}
|
|
MTTNodesetHasNewLayout(node, false);
|
|
float left = MTTNodeLayoutGetLeft(node);
|
|
float top = MTTNodeLayoutGetTop(node);
|
|
if (!MTTNodeGetParent(_nodeRef)) {
|
|
left = MTTNodeLayoutGetPosition(_nodeRef, CSSLeft);
|
|
top = MTTNodeLayoutGetPosition(_nodeRef, CSSTop);
|
|
if (isnan(left)) {
|
|
left = 0;
|
|
}
|
|
if (isnan(top)) {
|
|
top = 0;
|
|
}
|
|
}
|
|
float width = MTTNodeLayoutGetWidth(node);
|
|
float height = MTTNodeLayoutGetHeight(node);
|
|
CGPoint absoluteTopLeft = { absolutePosition.x + left, absolutePosition.y + top };
|
|
|
|
CGPoint absoluteBottomRight = { absolutePosition.x + left + width,
|
|
absolutePosition.y + top + height };
|
|
|
|
CGRect frame = { {
|
|
HippyRoundPixelValue(left),
|
|
HippyRoundPixelValue(top),
|
|
},
|
|
{ HippyRoundPixelValue(absoluteBottomRight.x - absoluteTopLeft.x), HippyRoundPixelValue(absoluteBottomRight.y - absoluteTopLeft.y) } };
|
|
|
|
if (!CGRectEqualToRect(frame, _frame) ||
|
|
_visibilityChanged) {
|
|
_frame = frame;
|
|
[viewsWithNewFrame addObject:self];
|
|
_visibilityChanged = NO;
|
|
}
|
|
|
|
absolutePosition.x += MTTNodeLayoutGetLeft(node);
|
|
absolutePosition.y += MTTNodeLayoutGetTop(node);
|
|
|
|
[self applyLayoutToChildren:node viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition];
|
|
}
|
|
|
|
- (void)applyLayoutToChildren:(MTTNodeRef)node
|
|
viewsWithNewFrame:(NSMutableSet<HippyShadowView *> *)viewsWithNewFrame
|
|
absolutePosition:(CGPoint)absolutePosition {
|
|
for (unsigned int i = 0; i < MTTNodeChildCount(node); ++i) {
|
|
HippyShadowView *child = (HippyShadowView *)_hippySubviews[i];
|
|
[child applyLayoutNode:MTTNodeGetChild(node, i) viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition];
|
|
}
|
|
}
|
|
|
|
- (NSDictionary<NSString *, id> *)processUpdatedProperties:(NSMutableSet<HippyApplierBlock> *)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 didUpdateHippySubviews];
|
|
[applierBlocks addObject:^(NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
UIView *view = viewRegistry[self->_hippyTag];
|
|
[view clearSortedSubviews];
|
|
[view didUpdateHippySubviews];
|
|
}];
|
|
}
|
|
|
|
if (!_backgroundColor) {
|
|
UIColor *parentBackgroundColor = parentProperties[HippyBackgroundColorProp];
|
|
if (parentBackgroundColor) {
|
|
[applierBlocks addObject:^(NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
UIView *view = viewRegistry[self->_hippyTag];
|
|
[view hippySetInheritedBackgroundColor: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[HippyBackgroundColorProp] = [UIColor clearColor];
|
|
} else {
|
|
properties[HippyBackgroundColorProp] = _backgroundColor;
|
|
}
|
|
return properties;
|
|
}
|
|
return parentProperties;
|
|
}
|
|
|
|
- (void)collectUpdatedProperties:(NSMutableSet<HippyApplierBlock> *)applierBlocks parentProperties:(NSDictionary<NSString *, id> *)parentProperties {
|
|
if (_propagationLifecycle == HippyUpdateLifecycleComputed && [parentProperties isEqualToDictionary:_lastParentProperties]) {
|
|
return;
|
|
}
|
|
_propagationLifecycle = HippyUpdateLifecycleComputed;
|
|
_lastParentProperties = parentProperties;
|
|
NSDictionary<NSString *, id> *nextProps = [self processUpdatedProperties:applierBlocks parentProperties:parentProperties];
|
|
for (HippyShadowView *child in _hippySubviews) {
|
|
[child collectUpdatedProperties:applierBlocks parentProperties:nextProps];
|
|
}
|
|
}
|
|
|
|
- (NSDictionary<NSString *, id> *)processUpdatedProperties:(NSMutableSet<HippyApplierBlock> *)applierBlocks
|
|
virtualApplierBlocks:(__unused NSMutableSet<HippyApplierVirtualBlock> *)virtualApplierBlocks
|
|
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 didUpdateHippySubviews];
|
|
[applierBlocks addObject:^(NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
UIView *view = viewRegistry[self->_hippyTag];
|
|
[view clearSortedSubviews];
|
|
[view didUpdateHippySubviews];
|
|
}];
|
|
}
|
|
|
|
if (!_backgroundColor) {
|
|
UIColor *parentBackgroundColor = parentProperties[HippyBackgroundColorProp];
|
|
if (parentBackgroundColor) {
|
|
[applierBlocks addObject:^(NSDictionary<NSNumber *, UIView *> *viewRegistry) {
|
|
UIView *view = viewRegistry[self->_hippyTag];
|
|
[view hippySetInheritedBackgroundColor: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[HippyBackgroundColorProp] = [UIColor clearColor];
|
|
} else {
|
|
properties[HippyBackgroundColorProp] = _backgroundColor;
|
|
}
|
|
return properties;
|
|
}
|
|
return parentProperties;
|
|
}
|
|
|
|
- (void)collectUpdatedProperties:(NSMutableSet<HippyApplierBlock> *)applierBlocks
|
|
virtualApplierBlocks:(NSMutableSet<HippyApplierVirtualBlock> *)virtualApplierBlocks
|
|
parentProperties:(NSDictionary<NSString *, id> *)parentProperties {
|
|
if (_propagationLifecycle == HippyUpdateLifecycleComputed && [parentProperties isEqualToDictionary:_lastParentProperties]) {
|
|
return;
|
|
}
|
|
_propagationLifecycle = HippyUpdateLifecycleComputed;
|
|
_lastParentProperties = parentProperties;
|
|
NSDictionary<NSString *, id> *nextProps = [self processUpdatedProperties:applierBlocks virtualApplierBlocks:virtualApplierBlocks
|
|
parentProperties:parentProperties];
|
|
for (HippyShadowView *child in _hippySubviews) {
|
|
[child collectUpdatedProperties:applierBlocks virtualApplierBlocks:virtualApplierBlocks parentProperties:nextProps];
|
|
}
|
|
}
|
|
|
|
- (void)collectUpdatedFrames:(NSMutableSet<HippyShadowView *> *)viewsWithNewFrame
|
|
withFrame:(CGRect)frame
|
|
hidden:(BOOL)hidden
|
|
absolutePosition:(CGPoint)absolutePosition {
|
|
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)) {
|
|
MTTNodeStyleSetPositionType(_nodeRef, PositionTypeAbsolute);
|
|
MTTNodeStyleSetWidth(_nodeRef, CGRectGetWidth(frame));
|
|
MTTNodeStyleSetHeight(_nodeRef, CGRectGetHeight(frame));
|
|
MTTNodeStyleSetPosition(_nodeRef, CSSLeft, frame.origin.x);
|
|
MTTNodeStyleSetPosition(_nodeRef, CSSTop, frame.origin.y);
|
|
}
|
|
|
|
NSWritingDirection direction = [[HippyI18nUtils sharedInstance] writingDirectionForCurrentAppLanguage];
|
|
MTTDirection nodeDirection = (NSWritingDirectionRightToLeft == direction) ? DirectionRTL : DirectionLTR;
|
|
nodeDirection = self.layoutDirection != DirectionInherit ? self.layoutDirection : nodeDirection;
|
|
MTTNodeDoLayout(_nodeRef, frame.size.width, frame.size.height, nodeDirection);
|
|
[self applyLayoutNode:_nodeRef viewsWithNewFrame:viewsWithNewFrame absolutePosition:absolutePosition];
|
|
}
|
|
|
|
- (CGRect)measureLayoutRelativeToAncestor:(HippyShadowView *)ancestor {
|
|
CGPoint offset = CGPointZero;
|
|
NSInteger depth = 30; // max depth to search
|
|
HippyShadowView *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:(HippyShadowView *)ancestor {
|
|
NSInteger depth = 30; // max depth to search
|
|
HippyShadowView *shadowView = self;
|
|
while (depth && shadowView && shadowView != ancestor) {
|
|
shadowView = shadowView->_superview;
|
|
depth--;
|
|
}
|
|
return ancestor == shadowView;
|
|
}
|
|
|
|
- (instancetype)init {
|
|
if ((self = [super init])) {
|
|
_frame = CGRectMake(0, 0, NAN, NAN);
|
|
|
|
for (unsigned int ii = 0; ii < META_PROP_COUNT; ii++) {
|
|
_paddingMetaProps[ii] = NAN;
|
|
_marginMetaProps[ii] = NAN;
|
|
_borderMetaProps[ii] = NAN;
|
|
}
|
|
|
|
_newView = YES;
|
|
_propagationLifecycle = HippyUpdateLifecycleUninitialized;
|
|
_textLifecycle = HippyUpdateLifecycleUninitialized;
|
|
|
|
_hippySubviews = [NSMutableArray array];
|
|
|
|
_nodeRef = MTTNodeNewWithScaleFactor(HippyScreenScale());
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (BOOL)isHippyRootView {
|
|
return HippyIsHippyRootView(self.hippyTag);
|
|
}
|
|
|
|
- (void)dealloc {
|
|
MTTNodeFree(_nodeRef);
|
|
}
|
|
|
|
- (BOOL)isCSSLeafNode {
|
|
return NO;
|
|
}
|
|
|
|
- (void)dirtyPropagation {
|
|
if (_propagationLifecycle != HippyUpdateLifecycleDirtied) {
|
|
_propagationLifecycle = HippyUpdateLifecycleDirtied;
|
|
[_superview dirtyPropagation];
|
|
}
|
|
}
|
|
|
|
- (BOOL)isPropagationDirty {
|
|
return _propagationLifecycle != HippyUpdateLifecycleComputed;
|
|
}
|
|
|
|
- (void)dirtyText {
|
|
if (_textLifecycle != HippyUpdateLifecycleDirtied) {
|
|
_textLifecycle = HippyUpdateLifecycleDirtied;
|
|
[_superview dirtyText];
|
|
}
|
|
}
|
|
|
|
- (BOOL)isTextDirty {
|
|
return _textLifecycle != HippyUpdateLifecycleComputed;
|
|
}
|
|
|
|
- (void)setTextComputed {
|
|
_textLifecycle = HippyUpdateLifecycleComputed;
|
|
}
|
|
|
|
- (BOOL)isHidden {
|
|
return _hidden || [_visibility isEqualToString:@"hidden"];
|
|
}
|
|
|
|
- (void)setVisibility:(NSString *)visibility {
|
|
if (![_visibility isEqualToString:visibility]) {
|
|
_visibility = visibility;
|
|
_visibilityChanged = YES;
|
|
MTTNodeMarkDirty(self.nodeRef);
|
|
}
|
|
}
|
|
|
|
- (void)insertHippySubview:(HippyShadowView *)subview atIndex:(NSInteger)atIndex {
|
|
[_hippySubviews insertObject:subview atIndex:atIndex];
|
|
if (![self isCSSLeafNode]) {
|
|
MTTNodeInsertChild(_nodeRef, subview.nodeRef, (uint32_t)atIndex);
|
|
}
|
|
subview->_superview = self;
|
|
_didUpdateSubviews = YES;
|
|
[self dirtyText];
|
|
[self dirtyPropagation];
|
|
}
|
|
|
|
- (void)removeHippySubview:(HippyShadowView *)subview {
|
|
[subview dirtyText];
|
|
[subview dirtyPropagation];
|
|
_didUpdateSubviews = YES;
|
|
subview->_superview = nil;
|
|
[_hippySubviews removeObject:subview];
|
|
if (![self isCSSLeafNode]) {
|
|
MTTNodeRemoveChild(_nodeRef, subview.nodeRef);
|
|
}
|
|
}
|
|
|
|
- (NSArray<HippyShadowView *> *)hippySubviews {
|
|
return _hippySubviews;
|
|
}
|
|
|
|
- (HippyShadowView *)hippySuperview {
|
|
return _superview;
|
|
}
|
|
|
|
- (NSNumber *)hippyTagAtPoint:(CGPoint)point {
|
|
for (HippyShadowView *shadowView in _hippySubviews) {
|
|
if (CGRectContainsPoint(shadowView.frame, point)) {
|
|
CGPoint relativePoint = point;
|
|
CGPoint origin = shadowView.frame.origin;
|
|
relativePoint.x -= origin.x;
|
|
relativePoint.y -= origin.y;
|
|
return [shadowView hippyTagAtPoint:relativePoint];
|
|
}
|
|
}
|
|
return self.hippyTag;
|
|
}
|
|
|
|
- (NSString *)description {
|
|
NSString *description = super.description;
|
|
description = [[description substringToIndex:description.length - 1]
|
|
stringByAppendingFormat:@"; viewName: %@; hippyTag: %@; frame: %@>", self.viewName, self.hippyTag, 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 (HippyShadowView *subview in _hippySubviews) {
|
|
[subview addRecursiveDescriptionToString:string atLevel:level + 1];
|
|
}
|
|
}
|
|
|
|
- (NSString *)recursiveDescription {
|
|
NSMutableString *description = [NSMutableString string];
|
|
[self addRecursiveDescriptionToString:description atLevel:0];
|
|
return description;
|
|
}
|
|
|
|
// Margin
|
|
|
|
#define HIPPY_MARGIN_PROPERTY(prop, metaProp) \
|
|
-(void)setMargin##prop : (CGFloat)value { \
|
|
_marginMetaProps[META_PROP_##metaProp] = value; \
|
|
_recomputeMargin = YES; \
|
|
} \
|
|
-(CGFloat)margin##prop { \
|
|
return _marginMetaProps[META_PROP_##metaProp]; \
|
|
}
|
|
|
|
HIPPY_MARGIN_PROPERTY(, ALL)
|
|
HIPPY_MARGIN_PROPERTY(Vertical, VERTICAL)
|
|
HIPPY_MARGIN_PROPERTY(Horizontal, HORIZONTAL)
|
|
HIPPY_MARGIN_PROPERTY(Top, TOP)
|
|
HIPPY_MARGIN_PROPERTY(Left, LEFT)
|
|
HIPPY_MARGIN_PROPERTY(Bottom, BOTTOM)
|
|
HIPPY_MARGIN_PROPERTY(Right, RIGHT)
|
|
|
|
// Padding
|
|
|
|
#define HIPPY_PADDING_PROPERTY(prop, metaProp) \
|
|
-(void)setPadding##prop : (CGFloat)value { \
|
|
_paddingMetaProps[META_PROP_##metaProp] = value; \
|
|
_recomputePadding = YES; \
|
|
} \
|
|
-(CGFloat)padding##prop { \
|
|
return _paddingMetaProps[META_PROP_##metaProp]; \
|
|
}
|
|
|
|
HIPPY_PADDING_PROPERTY(, ALL)
|
|
HIPPY_PADDING_PROPERTY(Vertical, VERTICAL)
|
|
HIPPY_PADDING_PROPERTY(Horizontal, HORIZONTAL)
|
|
HIPPY_PADDING_PROPERTY(Top, TOP)
|
|
HIPPY_PADDING_PROPERTY(Left, LEFT)
|
|
HIPPY_PADDING_PROPERTY(Bottom, BOTTOM)
|
|
HIPPY_PADDING_PROPERTY(Right, RIGHT)
|
|
|
|
- (UIEdgeInsets)paddingAsInsets {
|
|
CGFloat top = MTTNodeLayoutGetPadding(_nodeRef, CSSTop);
|
|
if (isnan(top)) {
|
|
top = 0;
|
|
}
|
|
CGFloat left = MTTNodeLayoutGetPadding(_nodeRef, CSSLeft);
|
|
if (isnan(left)) {
|
|
left = 0;
|
|
}
|
|
CGFloat bottom = MTTNodeLayoutGetPadding(_nodeRef, CSSBottom);
|
|
if (isnan(bottom)) {
|
|
bottom = 0;
|
|
}
|
|
CGFloat right = MTTNodeLayoutGetPadding(_nodeRef, CSSRight);
|
|
if (isnan(right)) {
|
|
right = 0;
|
|
}
|
|
return UIEdgeInsetsMake(top, left, bottom, right);
|
|
}
|
|
|
|
// Border
|
|
#define HIPPY_BORDER_PROPERTY(prop, metaProp) \
|
|
-(void)setBorder##prop##Width : (CGFloat)value { \
|
|
_borderMetaProps[META_PROP_##metaProp] = value; \
|
|
_recomputeBorder = YES; \
|
|
} \
|
|
-(CGFloat)border##prop##Width { \
|
|
return _borderMetaProps[META_PROP_##metaProp]; \
|
|
}
|
|
|
|
HIPPY_BORDER_PROPERTY(, ALL)
|
|
HIPPY_BORDER_PROPERTY(Top, TOP)
|
|
HIPPY_BORDER_PROPERTY(Left, LEFT)
|
|
HIPPY_BORDER_PROPERTY(Bottom, BOTTOM)
|
|
HIPPY_BORDER_PROPERTY(Right, RIGHT)
|
|
|
|
// Dimensions
|
|
#define X5_DIMENSION_PROPERTY(setProp, getProp, cssProp) \
|
|
-(void)set##setProp : (CGFloat)value { \
|
|
MTTNodeStyleSet##cssProp(_nodeRef, value); \
|
|
[self dirtyText]; \
|
|
} \
|
|
-(CGFloat)getProp { \
|
|
return MTTNodeLayoutGet##cssProp(_nodeRef); \
|
|
}
|
|
X5_DIMENSION_PROPERTY(Width, width, Width)
|
|
X5_DIMENSION_PROPERTY(Height, height, Height)
|
|
X5_DIMENSION_PROPERTY(MinWidth, minWidth, MinWidth)
|
|
X5_DIMENSION_PROPERTY(MinHeight, minHeight, MinHeight)
|
|
X5_DIMENSION_PROPERTY(MaxWidth, maxWidth, MaxWidth)
|
|
X5_DIMENSION_PROPERTY(MaxHeight, maxHeight, MaxHeight)
|
|
|
|
// Position
|
|
#define X5_POSITION_PROPERTY(setProp, getProp, edge) \
|
|
-(void)set##setProp : (CGFloat)value { \
|
|
MTTNodeStyleSetPosition(_nodeRef, edge, value); \
|
|
[self dirtyText]; \
|
|
} \
|
|
-(CGFloat)getProp { \
|
|
return MTTNodeLayoutGetPosition(_nodeRef, edge); \
|
|
}
|
|
X5_POSITION_PROPERTY(Top, top, CSSTop)
|
|
X5_POSITION_PROPERTY(Right, right, CSSRight)
|
|
X5_POSITION_PROPERTY(Bottom, bottom, CSSBottom)
|
|
X5_POSITION_PROPERTY(Left, left, CSSLeft)
|
|
|
|
- (void)setFrame:(CGRect)frame {
|
|
if (!CGRectEqualToRect(frame, _frame)) {
|
|
_frame = frame;
|
|
MTTNodeStyleSetPosition(_nodeRef, CSSLeft, CGRectGetMinX(frame));
|
|
MTTNodeStyleSetPosition(_nodeRef, CSSTop, CGRectGetMinY(frame));
|
|
MTTNodeStyleSetWidth(_nodeRef, CGRectGetWidth(frame));
|
|
MTTNodeStyleSetHeight(_nodeRef, CGRectGetHeight(frame));
|
|
}
|
|
}
|
|
|
|
static inline void x5AssignSuggestedDimension(MTTNodeRef cssNode, Dimension dimension, CGFloat amount) {
|
|
if (amount != UIViewNoIntrinsicMetric) {
|
|
switch (dimension) {
|
|
case DimWidth:
|
|
if (isnan(MTTNodeLayoutGetWidth(cssNode))) {
|
|
MTTNodeStyleSetWidth(cssNode, amount);
|
|
}
|
|
break;
|
|
case DimHeight:
|
|
if (isnan(MTTNodeLayoutGetHeight(cssNode))) {
|
|
MTTNodeStyleSetHeight(cssNode, amount);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)setIntrinsicContentSize:(CGSize)size {
|
|
if (MTTNodeLayoutGetFlexGrow(_nodeRef) == 0.f && MTTNodeLayoutGetFlexShrink(_nodeRef) == 0.f) {
|
|
x5AssignSuggestedDimension(_nodeRef, DimHeight, size.height);
|
|
x5AssignSuggestedDimension(_nodeRef, DimWidth, size.width);
|
|
}
|
|
}
|
|
|
|
- (void)setTopLeft:(CGPoint)topLeft {
|
|
MTTNodeStyleSetPosition(_nodeRef, CSSLeft, topLeft.x);
|
|
MTTNodeStyleSetPosition(_nodeRef, CSSLeft, topLeft.y);
|
|
}
|
|
|
|
- (void)setSize:(CGSize)size {
|
|
MTTNodeStyleSetWidth(_nodeRef, size.width);
|
|
MTTNodeStyleSetHeight(_nodeRef, size.height);
|
|
}
|
|
|
|
- (void)setLayoutDirection:(MTTDirection)layoutDirection {
|
|
_layoutDirection = layoutDirection;
|
|
MTTNodeStyleSetDirection(_nodeRef, layoutDirection);
|
|
}
|
|
|
|
// Flex
|
|
|
|
- (void)setFlex:(CGFloat)value {
|
|
MTTNodeStyleSetFlex(_nodeRef, value);
|
|
}
|
|
|
|
#define X5_STYLE_PROPERTY(setProp, getProp, cssProp, type) \
|
|
-(void)set##setProp : (type)value { \
|
|
MTTNodeStyleSet##cssProp(_nodeRef, value); \
|
|
} \
|
|
-(type)getProp { \
|
|
return MTTNodeLayoutGet##cssProp(_nodeRef); \
|
|
}
|
|
|
|
X5_STYLE_PROPERTY(FlexGrow, flexGrow, FlexGrow, CGFloat)
|
|
X5_STYLE_PROPERTY(FlexShrink, flexShrink, FlexShrink, CGFloat)
|
|
X5_STYLE_PROPERTY(FlexBasis, flexBasis, FlexBasis, CGFloat)
|
|
X5_STYLE_PROPERTY(FlexDirection, flexDirection, FlexDirection, FlexDirection)
|
|
X5_STYLE_PROPERTY(JustifyContent, justifyContent, JustifyContent, FlexAlign)
|
|
X5_STYLE_PROPERTY(AlignSelf, alignSelf, AlignSelf, FlexAlign)
|
|
X5_STYLE_PROPERTY(AlignItems, alignItems, AlignItems, FlexAlign)
|
|
X5_STYLE_PROPERTY(Position, position, PositionType, PositionType)
|
|
X5_STYLE_PROPERTY(FlexWrap, flexWrap, FlexWrap, FlexWrapMode)
|
|
X5_STYLE_PROPERTY(Overflow, overflow, Overflow, OverflowType)
|
|
X5_STYLE_PROPERTY(DisplayType, displayType, Display, DisplayType)
|
|
|
|
- (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)didUpdateHippySubviews {
|
|
// Does nothing by default
|
|
}
|
|
|
|
- (void)didSetProps:(__unused NSArray<NSString *> *)changedProps {
|
|
if (_recomputePadding) {
|
|
HippyProcessMetaPropsPadding(_paddingMetaProps, _nodeRef);
|
|
}
|
|
if (_recomputeMargin) {
|
|
HippyProcessMetaPropsMargin(_marginMetaProps, _nodeRef);
|
|
}
|
|
if (_recomputeBorder) {
|
|
HippyProcessMetaPropsBorder(_borderMetaProps, _nodeRef);
|
|
}
|
|
_recomputeMargin = NO;
|
|
_recomputePadding = NO;
|
|
_recomputeBorder = NO;
|
|
}
|
|
|
|
- (void)hippySetFrame:(__unused CGRect)frame {
|
|
}
|
|
|
|
- (NSDictionary *)mergeProps:(NSDictionary *)props {
|
|
if (self.props == nil) {
|
|
self.props = props;
|
|
return self.props;
|
|
}
|
|
|
|
if ([_props isEqualToDictionary:props]) {
|
|
return @{};
|
|
}
|
|
|
|
NSMutableDictionary *needUpdatedProps = [[NSMutableDictionary alloc] initWithDictionary:props];
|
|
NSMutableArray<NSString *> *sameKeys = [NSMutableArray new];
|
|
[self.props enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull key, id _Nonnull obj, __unused BOOL *stop) {
|
|
if ([key isEqualToString:@"rootTag"]) {
|
|
return;
|
|
}
|
|
if (needUpdatedProps[key] == nil) {
|
|
needUpdatedProps[key] = [self defaultValueForKey:key];
|
|
} else {
|
|
if ([needUpdatedProps[key] isEqual:obj]) {
|
|
[sameKeys addObject:key];
|
|
}
|
|
}
|
|
}];
|
|
self.props = needUpdatedProps;
|
|
[needUpdatedProps removeObjectsForKeys:sameKeys];
|
|
return needUpdatedProps;
|
|
}
|
|
|
|
- (id)defaultValueForKey:(NSString *)key {
|
|
static dispatch_once_t onceToken;
|
|
static NSArray *layoutKeys = nil;
|
|
id ret = nil;
|
|
dispatch_once(&onceToken, ^{
|
|
layoutKeys = @[
|
|
@"top", @"left", @"bottom", @"right", @"width", @"height", @"minWidth", @"maxWidth", @"minHeight", @"maxHeight", @"borderTopWidth",
|
|
@"borderRightWidth", @"borderBottomWidth", @"borderLeftWidth", @"borderWidth", @"marginTop", @"marginLeft", @"marginBottom",
|
|
@"marginRight", @"marginVertical", @"marginHorizontal", @"paddingTpp", @"paddingRight", @"paddingBottom", @"paddingLeft",
|
|
@"paddingVertical", @"paddingHorizontal"
|
|
];
|
|
});
|
|
if ([layoutKeys containsObject:key]) {
|
|
ret = @(NAN);
|
|
} else if ([key isEqualToString:@"display"]) {
|
|
ret = @"block";
|
|
} else {
|
|
ret = (id)kCFNull;
|
|
}
|
|
return ret;
|
|
}
|
|
@end
|