From 8715b57d3d5c96977a3821c65d98651c507a77ca Mon Sep 17 00:00:00 2001 From: Joe Vilches Date: Mon, 26 Aug 2024 10:57:12 -0700 Subject: [PATCH] Decouple background color from border drawing (#46190) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/46190 Right now the background color is tightly coupled with border drawing. For starters, I find this a bit confusing as one would not assume they are very closely related. But this also causes a bug around not being able to properly clip to the padding box, because if we did this we would clip the background color and transparent borders would look wrong. This would also block a fix related to how borders display with clipped content that is coming in the later diffs. If we decide to use the border image, then we cannot properly display things like images since they would be on top of this image (otherwise background color shows through). Changelog: [Internal] Reviewed By: lenaic Differential Revision: D61248625 --- .../View/RCTViewComponentView.mm | 49 ++++++++++++++----- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm index 053366aef1c5fb..1d9570cd764d06 100644 --- a/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm +++ b/packages/react-native/React/Fabric/Mounting/ComponentViews/View/RCTViewComponentView.mm @@ -28,8 +28,11 @@ using namespace facebook::react; +const CGFloat BACKGROUND_COLOR_ZPOSITION = -1024.0f; + @implementation RCTViewComponentView { UIColor *_backgroundColor; + CALayer *_backgroundColorLayer; __weak CALayer *_borderLayer; CALayer *_boxShadowLayer; CALayer *_filterLayer; @@ -524,6 +527,10 @@ - (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics _containerView.frame = CGRectMake(0, 0, self.layer.bounds.size.width, self.layer.bounds.size.height); } + if (_backgroundColorLayer) { + _backgroundColorLayer.frame = CGRectMake(0, 0, self.layer.bounds.size.width, self.layer.bounds.size.height); + } + if ((_props->transformOrigin.isSet() || _props->transform.operations.size() > 0) && layoutMetrics.frame.size != oldLayoutMetrics.frame.size) { auto newTransform = _props->resolveTransform(layoutMetrics); @@ -774,8 +781,6 @@ - (void)invalidateLayer [self setHoverStyle:hoverStyle]; } #endif - - // Stage 2. Border Rendering const bool useCoreAnimationBorderRendering = borderMetrics.borderColors.isUniform() && borderMetrics.borderWidths.isUniform() && borderMetrics.borderStyles.isUniform() && borderMetrics.borderRadii.isUniform() && @@ -787,8 +792,35 @@ - (void)invalidateLayer (colorComponentsFromColor(borderMetrics.borderColors.left).alpha == 0 && (*borderMetrics.borderColors.left).getUIColor() != nullptr)); + // background color CGColorRef backgroundColor = [_backgroundColor resolvedColorWithTraitCollection:self.traitCollection].CGColor; + // The reason we sometimes do not set self.layer's backgroundColor is because + // we want to support non-uniform border radii, which apple does not natively + // support. To get this behavior we need to create a CGPath in the shape that + // we want. If we mask self.layer to this path, we would be clipping subviews + // which we may not want to do. The generalized solution in this case is just + // create a new layer + if (useCoreAnimationBorderRendering) { + [_backgroundColorLayer removeFromSuperlayer]; + _backgroundColorLayer = nil; + layer.backgroundColor = backgroundColor; + } else { + layer.backgroundColor = nil; + if (!_backgroundColorLayer) { + _backgroundColorLayer = [CALayer layer]; + _backgroundColorLayer.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height); + _backgroundColorLayer.zPosition = BACKGROUND_COLOR_ZPOSITION; + [self.layer addSublayer:_backgroundColorLayer]; + } + + CAShapeLayer *maskLayer = [self + createMaskLayer:self.bounds + cornerInsets:RCTGetCornerInsets(RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), UIEdgeInsetsZero)]; + _backgroundColorLayer.backgroundColor = backgroundColor; + _backgroundColorLayer.mask = maskLayer; + } + // Stage 2. Border Rendering if (useCoreAnimationBorderRendering) { layer.mask = nil; [_borderLayer removeFromSuperlayer]; @@ -798,21 +830,17 @@ - (void)invalidateLayer layer.borderColor = borderColor; CGColorRelease(borderColor); layer.cornerRadius = (CGFloat)borderMetrics.borderRadii.topLeft.horizontal; - layer.cornerCurve = CornerCurveFromBorderCurve(borderMetrics.borderCurves.topLeft); - - layer.backgroundColor = backgroundColor; } else { if (!_borderLayer) { CALayer *borderLayer = [CALayer new]; - borderLayer.zPosition = -1024.0f; + borderLayer.zPosition = BACKGROUND_COLOR_ZPOSITION + 1; borderLayer.frame = layer.bounds; borderLayer.magnificationFilter = kCAFilterNearest; [layer addSublayer:borderLayer]; _borderLayer = borderLayer; } - layer.backgroundColor = nil; layer.borderWidth = 0; layer.borderColor = nil; layer.cornerRadius = 0; @@ -824,8 +852,8 @@ - (void)invalidateLayer RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths), borderColors, - backgroundColor, - self.clipsToBounds); + [UIColor clearColor].CGColor, + NO); RCTReleaseRCTBorderColors(borderColors); @@ -959,8 +987,7 @@ - (void)invalidateLayer gradientLayer.mask = maskLayer; } - // border layer should appear above gradient layers to make sure that the border is visible - gradientLayer.zPosition = _borderLayer.zPosition - 1; + gradientLayer.zPosition = BACKGROUND_COLOR_ZPOSITION; [self.layer addSublayer:gradientLayer]; [_gradientLayers addObject:gradientLayer];