From 7653f7600e1fb1883743c96fccd9ab3e9eb11d89 Mon Sep 17 00:00:00 2001 From: Joe Vilches Date: Fri, 30 Aug 2024 02:00:19 -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 fbshipit-source-id: ad398bbcfb69edc7d61362920773b51abea81d08 --- .../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 053366aef1c..1d9570cd764 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 @@ using namespace facebook::react; _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 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle) [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 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle) (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 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle) 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 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle) RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths), borderColors, - backgroundColor, - self.clipsToBounds); + [UIColor clearColor].CGColor, + NO); RCTReleaseRCTBorderColors(borderColors); @@ -959,8 +987,7 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle) 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];