blob: adf1652a1d63afe5b2225069ccc6dad74af3c800 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 "WXComponent+Layout.h"
#import "WXComponent_internal.h"
#import "WXTransform.h"
#import "WXAssert.h"
#import "WXComponent_internal.h"
#import "WXSDKInstance_private.h"
#import "WXComponent+BoxShadow.h"
#import "WXLayoutDefine.h"
@implementation WXComponent (Layout)
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
#pragma mark Public
- (void)setNeedsLayout
{
_isLayoutDirty = YES;
WXComponent *supercomponent = [self supercomponent];
if(supercomponent){
[supercomponent setNeedsLayout];
}
}
- (BOOL)needsLayout
{
return _isLayoutDirty;
}
- (CGSize (^)(CGSize))measureBlock
{
return nil;
}
- (void)layoutDidFinish
{
WXAssertMainThread();
}
#pragma mark Private
- (void)_initCSSNodeWithStyles:(NSDictionary *)styles
{
_cssNode = new_css_node();
_cssNode->print = cssNodePrint;
_cssNode->get_child = cssNodeGetChild;
_cssNode->is_dirty = cssNodeIsDirty;
if ([self measureBlock]) {
_cssNode->measure = cssNodeMeasure;
}
_cssNode->context = (__bridge void *)self;
[self _recomputeCSSNodeChildren];
[self _fillCSSNode:styles];
// To be in conformity with Android/Web, hopefully remove this in the future.
if ([self.ref isEqualToString:WX_SDK_ROOT_REF]) {
if (isUndefined(_cssNode->style.dimensions[CSS_HEIGHT]) && self.weexInstance.frame.size.height) {
_cssNode->style.dimensions[CSS_HEIGHT] = self.weexInstance.frame.size.height;
}
if (isUndefined(_cssNode->style.dimensions[CSS_WIDTH]) && self.weexInstance.frame.size.width) {
_cssNode->style.dimensions[CSS_WIDTH] = self.weexInstance.frame.size.width;
}
}
}
- (void)_updateCSSNodeStyles:(NSDictionary *)styles
{
[self _fillCSSNode:styles];
}
-(void)_resetCSSNodeStyles:(NSArray *)styles
{
[self _resetCSSNode:styles];
}
- (void)_recomputeCSSNodeChildren
{
_cssNode->children_count = (int)[self _childrenCountForLayout];
}
- (NSUInteger)_childrenCountForLayout
{
NSArray *subcomponents = _subcomponents;
NSUInteger count = subcomponents.count;
for (WXComponent *component in subcomponents) {
if (!component->_isNeedJoinLayoutSystem) {
count--;
}
}
return (int)(count);
}
- (void)_frameDidCalculated:(BOOL)isChanged
{
WXAssertComponentThread();
if ([self isViewLoaded] && isChanged && [self isViewFrameSyncWithCalculated]) {
__weak typeof(self) weakSelf = self;
[self.weexInstance.componentManager _addUITask:^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf->_transform && !CATransform3DEqualToTransform(strongSelf.layer.transform, CATransform3DIdentity)) {
// From the UIView's frame documentation:
// https://developer.apple.com/reference/uikit/uiview#//apple_ref/occ/instp/UIView/frame
// Warning : If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.
// So layer's transform must be reset to CATransform3DIdentity before setFrame, otherwise frame will be incorrect
strongSelf.layer.transform = CATransform3DIdentity;
}
if (!CGRectEqualToRect(strongSelf.view.frame,strongSelf.calculatedFrame)) {
strongSelf.view.frame = strongSelf.calculatedFrame;
strongSelf->_absolutePosition = CGPointMake(NAN, NAN);
[strongSelf configBoxShadow:_boxShadow];
} else {
if (![strongSelf equalBoxShadow:_boxShadow withBoxShadow:_lastBoxShadow]) {
[strongSelf configBoxShadow:_boxShadow];
}
}
[self _resetNativeBorderRadius];
if (strongSelf->_transform) {
[strongSelf->_transform applyTransformForView:strongSelf.view];
}
if (strongSelf->_backgroundImage) {
[strongSelf setGradientLayer];
}
[strongSelf setNeedsDisplay];
}];
}
}
- (void)_calculateFrameWithSuperAbsolutePosition:(CGPoint)superAbsolutePosition
gatherDirtyComponents:(NSMutableSet<WXComponent *> *)dirtyComponents
{
WXAssertComponentThread();
if (!_cssNode->layout.should_update) {
return;
}
_cssNode->layout.should_update = false;
_isLayoutDirty = NO;
CGRect newFrame = CGRectMake(WXRoundPixelValue(_cssNode->layout.position[CSS_LEFT]),
WXRoundPixelValue(_cssNode->layout.position[CSS_TOP]),
WXRoundPixelValue(_cssNode->layout.dimensions[CSS_WIDTH]),
WXRoundPixelValue(_cssNode->layout.dimensions[CSS_HEIGHT]));
BOOL isFrameChanged = NO;
if (!CGRectEqualToRect(newFrame, _calculatedFrame)) {
isFrameChanged = YES;
_calculatedFrame = newFrame;
[dirtyComponents addObject:self];
}
CGPoint newAbsolutePosition = [self computeNewAbsolutePosition:superAbsolutePosition];
_cssNode->layout.dimensions[CSS_WIDTH] = CSS_UNDEFINED;
_cssNode->layout.dimensions[CSS_HEIGHT] = CSS_UNDEFINED;
_cssNode->layout.position[CSS_LEFT] = 0;
_cssNode->layout.position[CSS_TOP] = 0;
[self _frameDidCalculated:isFrameChanged];
NSArray * subcomponents = [_subcomponents copy];
for (WXComponent *subcomponent in subcomponents) {
[subcomponent _calculateFrameWithSuperAbsolutePosition:newAbsolutePosition gatherDirtyComponents:dirtyComponents];
}
}
- (CGPoint)computeNewAbsolutePosition:(CGPoint)superAbsolutePosition
{
// Not need absolutePosition any more
return superAbsolutePosition;
}
- (void)_layoutDidFinish
{
WXAssertMainThread();
if (_positionType == WXPositionTypeSticky) {
[self.ancestorScroller adjustSticky];
}
[self layoutDidFinish];
}
#define WX_STYLE_FILL_CSS_NODE(key, cssProp, type)\
do {\
id value = styles[@#key];\
if (value) {\
typeof(_cssNode->style.cssProp) convertedValue = (typeof(_cssNode->style.cssProp))[WXConvert type:value];\
_cssNode->style.cssProp = convertedValue;\
[self setNeedsLayout];\
}\
} while(0);
#define WX_STYLE_FILL_CSS_NODE_PIXEL(key, cssProp)\
do {\
id value = styles[@#key];\
if (value) {\
CGFloat pixel = [self WXPixelType:value];\
if (isnan(pixel)) {\
WXLogError(@"Invalid NaN value for style:%@, ref:%@", @#key, self.ref);\
} else {\
_cssNode->style.cssProp = pixel;\
[self setNeedsLayout];\
}\
}\
} while(0);
#define WX_STYLE_FILL_CSS_NODE_ALL_DIRECTION(key, cssProp)\
do {\
WX_STYLE_FILL_CSS_NODE_PIXEL(key, cssProp[CSS_TOP])\
WX_STYLE_FILL_CSS_NODE_PIXEL(key, cssProp[CSS_LEFT])\
WX_STYLE_FILL_CSS_NODE_PIXEL(key, cssProp[CSS_RIGHT])\
WX_STYLE_FILL_CSS_NODE_PIXEL(key, cssProp[CSS_BOTTOM])\
} while(0);
- (CGFloat)WXPixelType:(id)value
{
return [WXConvert WXPixelType:value scaleFactor:self.weexInstance.pixelScaleFactor];
}
- (void)_fillCSSNode:(NSDictionary *)styles
{
// flex
WX_STYLE_FILL_CSS_NODE(flex, flex, CGFloat)
WX_STYLE_FILL_CSS_NODE(flexDirection, flex_direction, css_flex_direction_t)
WX_STYLE_FILL_CSS_NODE(alignItems, align_items, css_align_t)
WX_STYLE_FILL_CSS_NODE(alignSelf, align_self, css_align_t)
WX_STYLE_FILL_CSS_NODE(flexWrap, flex_wrap, css_wrap_type_t)
WX_STYLE_FILL_CSS_NODE(justifyContent, justify_content, css_justify_t)
// position
WX_STYLE_FILL_CSS_NODE(position, position_type, css_position_type_t)
WX_STYLE_FILL_CSS_NODE_PIXEL(top, position[CSS_TOP])
WX_STYLE_FILL_CSS_NODE_PIXEL(left, position[CSS_LEFT])
WX_STYLE_FILL_CSS_NODE_PIXEL(right, position[CSS_RIGHT])
WX_STYLE_FILL_CSS_NODE_PIXEL(bottom, position[CSS_BOTTOM])
// dimension
WX_STYLE_FILL_CSS_NODE_PIXEL(width, dimensions[CSS_WIDTH])
WX_STYLE_FILL_CSS_NODE_PIXEL(height, dimensions[CSS_HEIGHT])
WX_STYLE_FILL_CSS_NODE_PIXEL(minWidth, minDimensions[CSS_WIDTH])
WX_STYLE_FILL_CSS_NODE_PIXEL(minHeight, minDimensions[CSS_HEIGHT])
WX_STYLE_FILL_CSS_NODE_PIXEL(maxWidth, maxDimensions[CSS_WIDTH])
WX_STYLE_FILL_CSS_NODE_PIXEL(maxHeight, maxDimensions[CSS_HEIGHT])
// margin
WX_STYLE_FILL_CSS_NODE_ALL_DIRECTION(margin, margin)
WX_STYLE_FILL_CSS_NODE_PIXEL(marginTop, margin[CSS_TOP])
WX_STYLE_FILL_CSS_NODE_PIXEL(marginLeft, margin[CSS_LEFT])
WX_STYLE_FILL_CSS_NODE_PIXEL(marginRight, margin[CSS_RIGHT])
WX_STYLE_FILL_CSS_NODE_PIXEL(marginBottom, margin[CSS_BOTTOM])
// border
WX_STYLE_FILL_CSS_NODE_ALL_DIRECTION(borderWidth, border)
WX_STYLE_FILL_CSS_NODE_PIXEL(borderTopWidth, border[CSS_TOP])
WX_STYLE_FILL_CSS_NODE_PIXEL(borderLeftWidth, border[CSS_LEFT])
WX_STYLE_FILL_CSS_NODE_PIXEL(borderRightWidth, border[CSS_RIGHT])
WX_STYLE_FILL_CSS_NODE_PIXEL(borderBottomWidth, border[CSS_BOTTOM])
// padding
WX_STYLE_FILL_CSS_NODE_ALL_DIRECTION(padding, padding)
WX_STYLE_FILL_CSS_NODE_PIXEL(paddingTop, padding[CSS_TOP])
WX_STYLE_FILL_CSS_NODE_PIXEL(paddingLeft, padding[CSS_LEFT])
WX_STYLE_FILL_CSS_NODE_PIXEL(paddingRight, padding[CSS_RIGHT])
WX_STYLE_FILL_CSS_NODE_PIXEL(paddingBottom, padding[CSS_BOTTOM])
}
#define WX_STYLE_RESET_CSS_NODE(key, cssProp, defaultValue)\
do {\
if (styles && [styles containsObject:@#key]) {\
_cssNode->style.cssProp = defaultValue;\
[self setNeedsLayout];\
}\
} while(0);
#define WX_STYLE_RESET_CSS_NODE_ALL_DIRECTION(key, cssProp, defaultValue)\
do {\
WX_STYLE_RESET_CSS_NODE(key, cssProp[CSS_TOP], defaultValue)\
WX_STYLE_RESET_CSS_NODE(key, cssProp[CSS_LEFT], defaultValue)\
WX_STYLE_RESET_CSS_NODE(key, cssProp[CSS_RIGHT], defaultValue)\
WX_STYLE_RESET_CSS_NODE(key, cssProp[CSS_BOTTOM], defaultValue)\
} while(0);
- (void)_resetCSSNode:(NSArray *)styles;
{
// flex
WX_STYLE_RESET_CSS_NODE(flex, flex, 0.0)
WX_STYLE_RESET_CSS_NODE(flexDirection, flex_direction, CSS_FLEX_DIRECTION_COLUMN)
WX_STYLE_RESET_CSS_NODE(alignItems, align_items, CSS_ALIGN_STRETCH)
WX_STYLE_RESET_CSS_NODE(alignSelf, align_self, CSS_ALIGN_AUTO)
WX_STYLE_RESET_CSS_NODE(flexWrap, flex_wrap, CSS_NOWRAP)
WX_STYLE_RESET_CSS_NODE(justifyContent, justify_content, CSS_JUSTIFY_FLEX_START)
// position
WX_STYLE_RESET_CSS_NODE(position, position_type, CSS_POSITION_RELATIVE)
WX_STYLE_RESET_CSS_NODE(top, position[CSS_TOP], CSS_UNDEFINED)
WX_STYLE_RESET_CSS_NODE(left, position[CSS_LEFT], CSS_UNDEFINED)
WX_STYLE_RESET_CSS_NODE(right, position[CSS_RIGHT], CSS_UNDEFINED)
WX_STYLE_RESET_CSS_NODE(bottom, position[CSS_BOTTOM], CSS_UNDEFINED)
// dimension
WX_STYLE_RESET_CSS_NODE(width, dimensions[CSS_WIDTH], CSS_UNDEFINED)
WX_STYLE_RESET_CSS_NODE(height, dimensions[CSS_HEIGHT], CSS_UNDEFINED)
WX_STYLE_RESET_CSS_NODE(minWidth, minDimensions[CSS_WIDTH], CSS_UNDEFINED)
WX_STYLE_RESET_CSS_NODE(minHeight, minDimensions[CSS_HEIGHT], CSS_UNDEFINED)
WX_STYLE_RESET_CSS_NODE(maxWidth, maxDimensions[CSS_WIDTH], CSS_UNDEFINED)
WX_STYLE_RESET_CSS_NODE(maxHeight, maxDimensions[CSS_HEIGHT], CSS_UNDEFINED)
// margin
WX_STYLE_RESET_CSS_NODE_ALL_DIRECTION(margin, margin, 0.0)
WX_STYLE_RESET_CSS_NODE(marginTop, margin[CSS_TOP], 0.0)
WX_STYLE_RESET_CSS_NODE(marginLeft, margin[CSS_LEFT], 0.0)
WX_STYLE_RESET_CSS_NODE(marginRight, margin[CSS_RIGHT], 0.0)
WX_STYLE_RESET_CSS_NODE(marginBottom, margin[CSS_BOTTOM], 0.0)
// border
WX_STYLE_RESET_CSS_NODE_ALL_DIRECTION(borderWidth, border, 0.0)
WX_STYLE_RESET_CSS_NODE(borderTopWidth, border[CSS_TOP], 0.0)
WX_STYLE_RESET_CSS_NODE(borderLeftWidth, border[CSS_LEFT], 0.0)
WX_STYLE_RESET_CSS_NODE(borderRightWidth, border[CSS_RIGHT], 0.0)
WX_STYLE_RESET_CSS_NODE(borderBottomWidth, border[CSS_BOTTOM], 0.0)
// padding
WX_STYLE_RESET_CSS_NODE_ALL_DIRECTION(padding, padding, 0.0)
WX_STYLE_RESET_CSS_NODE(paddingTop, padding[CSS_TOP], 0.0)
WX_STYLE_RESET_CSS_NODE(paddingLeft, padding[CSS_LEFT], 0.0)
WX_STYLE_RESET_CSS_NODE(paddingRight, padding[CSS_RIGHT], 0.0)
WX_STYLE_RESET_CSS_NODE(paddingBottom, padding[CSS_BOTTOM], 0.0)
}
#pragma mark CSS Node Override
static void cssNodePrint(void *context)
{
WXComponent *component = (__bridge WXComponent *)context;
// TODO:
printf("%s:%s ", component.ref.UTF8String, component->_type.UTF8String);
}
static css_node_t * cssNodeGetChild(void *context, int i)
{
WXComponent *component = (__bridge WXComponent *)context;
NSArray *subcomponents = component->_subcomponents;
for (int j = 0; j <= i && j < subcomponents.count; j++) {
WXComponent *child = subcomponents[j];
if (!child->_isNeedJoinLayoutSystem) {
i++;
}
}
if(i >= 0 && i < subcomponents.count){
WXComponent *child = subcomponents[i];
return child->_cssNode;
}
WXAssert(NO, @"Can not find component:%@'s css node child at index: %ld, totalCount:%ld", component, i, subcomponents.count);
return NULL;
}
static bool cssNodeIsDirty(void *context)
{
WXAssertComponentThread();
WXComponent *component = (__bridge WXComponent *)context;
BOOL needsLayout = [component needsLayout];
return needsLayout;
}
static css_dim_t cssNodeMeasure(void *context, float width, css_measure_mode_t widthMode, float height, css_measure_mode_t heightMode)
{
WXComponent *component = (__bridge WXComponent *)context;
CGSize (^measureBlock)(CGSize) = [component measureBlock];
if (!measureBlock) {
return (css_dim_t){NAN, NAN};
}
CGSize constrainedSize = CGSizeMake(width, height);
CGSize resultSize = measureBlock(constrainedSize);
return (css_dim_t){resultSize.width, resultSize.height};
}
@end