mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Get inset box shadow to play nicely with borders (#45681)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/45681 This diff fixes 2 related issues that arise when we add a border to the view with box shadow 1) The shadow fills the padding box and not the border box. To fix this we just need to subtract the edge insets (border width) from both the shadow size and the clear region size. We also need to change the clipping area to clip anything outside the padding box 2) The corner radius of the clear region is based on the corner insets, so border radius - border width The first change required a bit of thinking on my part to remember what bits of the crazy arithmetic here needed to change. So I refactored a bit: * The general theme now is that all of the rects are derived from one another, and make use of CGRectOffset and CGRectInset to make their necessary adjustments. * We introduce `shadowFrame` which is just the frame of the shadow area - agnostic of things like blur padding and offscreen shenanigans. So its the size of the layer insetted by the border widths. * From this we can derive our 2 offscreen rects. The `shadowCastingRect` outsets the shadow frame by the blurRadius, while the `clearRegionRect` insets by spread distance. We then use `CGRectOffset` to push it offscreen. We save this offset so we can use it later to get the CG shadow back in place (since this is all originally derived from `shadowFrame`. * There is now a single place that dictates the size of the shadow (`shadowFrame`), and a single place that dictates the offset to push our rects offscreen. The necessary change to trace padding box and not content box therefore just needs to change `shadowFrame` as opposed to 4 other spots. * Additionally, when we offset, we do not need to worry about things like spread and blur, since `CGRectInset` takes care of that along with the size Changelog: [Internal] Reviewed By: NickGerleman Differential Revision: D60201959 fbshipit-source-id: 4ecf0e0db8ce9d54f08e89adec94d50eb19a26a3
This commit is contained in:
committed by
Facebook GitHub Bot
parent
4fef34e1fd
commit
49a7a7d58b
+5
-2
@@ -838,8 +838,11 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle)
|
||||
_boxShadowLayer.zPosition = CGFLOAT_MIN;
|
||||
_boxShadowLayer.frame = RCTGetBoundingRect(_props->boxShadow, self.layer.frame.size);
|
||||
|
||||
UIImage *boxShadowImage =
|
||||
RCTGetBoxShadowImage(_props->boxShadow, RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), layer);
|
||||
UIImage *boxShadowImage = RCTGetBoxShadowImage(
|
||||
_props->boxShadow,
|
||||
RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii),
|
||||
RCTUIEdgeInsetsFromEdgeInsets(borderMetrics.borderWidths),
|
||||
layer);
|
||||
|
||||
_boxShadowLayer.contents = (id)boxShadowImage.CGImage;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <react/renderer/graphics/BoxShadow.h>
|
||||
|
||||
RCT_EXTERN UIImage *
|
||||
RCTGetBoxShadowImage(std::vector<facebook::react::BoxShadow> shadows, RCTCornerRadii cornerRadii, CALayer *layer);
|
||||
RCT_EXTERN UIImage *RCTGetBoxShadowImage(
|
||||
const std::vector<facebook::react::BoxShadow> &shadows,
|
||||
RCTCornerRadii cornerRadii,
|
||||
UIEdgeInsets edgeInsets,
|
||||
CALayer *layer);
|
||||
|
||||
RCT_EXTERN CGRect RCTGetBoundingRect(std::vector<facebook::react::BoxShadow> boxShadows, CGSize layerSize);
|
||||
RCT_EXTERN CGRect RCTGetBoundingRect(const std::vector<facebook::react::BoxShadow> &boxShadows, CGSize layerSize);
|
||||
|
||||
@@ -39,7 +39,7 @@ static RCTCornerRadii cornerRadiiForBoxShadow(RCTCornerRadii cornerRadii, CGFloa
|
||||
// Returns the smallest CGRect that will contain all shadows and the layer itself.
|
||||
// The origin represents the location of this box relative to the layer the shadows
|
||||
// are attached to.
|
||||
CGRect RCTGetBoundingRect(std::vector<BoxShadow> boxShadows, CGSize layerSize)
|
||||
CGRect RCTGetBoundingRect(const std::vector<BoxShadow> &boxShadows, CGSize layerSize)
|
||||
{
|
||||
CGFloat smallestX = 0;
|
||||
CGFloat smallestY = 0;
|
||||
@@ -76,6 +76,12 @@ static std::pair<std::vector<BoxShadow>, std::vector<BoxShadow>> splitBoxShadows
|
||||
return std::make_pair(outsetShadows, insetShadows);
|
||||
}
|
||||
|
||||
static CGRect insetRect(CGRect rect, CGFloat left, CGFloat top, CGFloat right, CGFloat bottom)
|
||||
{
|
||||
return CGRectMake(
|
||||
rect.origin.x + left, rect.origin.y + top, rect.size.width - right - left, rect.size.height - bottom - top);
|
||||
}
|
||||
|
||||
// Core graphics has support for shadows that looks similar to web and are very
|
||||
// fast to apply. The only issue is that this shadow does not take a spread
|
||||
// radius like on web. To get around this, we draw the shadow rect (the rect
|
||||
@@ -168,6 +174,7 @@ static void renderOutsetShadows(
|
||||
static void renderInsetShadows(
|
||||
std::vector<BoxShadow> &insetShadows,
|
||||
RCTCornerRadii cornerRadii,
|
||||
UIEdgeInsets edgeInsets,
|
||||
CALayer *layer,
|
||||
CGRect boundingRect,
|
||||
CGContextRef context)
|
||||
@@ -180,17 +187,19 @@ static void renderInsetShadows(
|
||||
// graphical state carrying over after this function returns
|
||||
CGContextSaveGState(context);
|
||||
|
||||
CGRect layerFrameRelativeToBoundingRect =
|
||||
CGRectMake(-boundingRect.origin.x, -boundingRect.origin.y, layer.bounds.size.width, layer.bounds.size.height);
|
||||
CGRect shadowFrame =
|
||||
insetRect(layerFrameRelativeToBoundingRect, edgeInsets.left, edgeInsets.top, edgeInsets.right, edgeInsets.bottom);
|
||||
|
||||
// First, create a clipping area so we only draw within the view's bounds.
|
||||
// If we do not do this, blur artifacts will show up outside the view.
|
||||
CGRect outerClippingRect = CGRectMake(0, 0, boundingRect.size.width, boundingRect.size.height);
|
||||
// Add the path twice so we only draw inside the view with the EO crop rule
|
||||
CGContextAddRect(context, outerClippingRect);
|
||||
CGContextAddRect(context, outerClippingRect);
|
||||
const RCTCornerInsets cornerInsetsForLayer = RCTGetCornerInsets(cornerRadii, UIEdgeInsetsZero);
|
||||
CGPathRef layerPath = RCTPathCreateWithRoundedRect(
|
||||
CGRectMake(-boundingRect.origin.x, -boundingRect.origin.y, layer.bounds.size.width, layer.bounds.size.height),
|
||||
cornerInsetsForLayer,
|
||||
nil);
|
||||
const RCTCornerInsets cornerInsetsForLayer = RCTGetCornerInsets(cornerRadii, edgeInsets);
|
||||
CGPathRef layerPath = RCTPathCreateWithRoundedRect(shadowFrame, cornerInsetsForLayer, nil);
|
||||
CGContextAddPath(context, layerPath);
|
||||
CGContextEOClip(context);
|
||||
CGPathRelease(layerPath);
|
||||
@@ -211,39 +220,29 @@ static void renderInsetShadows(
|
||||
// We also pad the size of the shadow rect by the blur radius so that the
|
||||
// edges of the shadow remain a solid color and do not blend with outside
|
||||
// of the view.
|
||||
CGSize shadowRectSize =
|
||||
CGSizeMake(layer.bounds.size.width + 2 * blurRadius, layer.bounds.size.height + 2 * blurRadius);
|
||||
CGSize clearRegionSize =
|
||||
CGSizeMake(layer.bounds.size.width - 2 * spreadDistance, layer.bounds.size.height - 2 * spreadDistance);
|
||||
CGRect shadowRect = CGRectMake(
|
||||
-fmax(shadowRectSize.width, clearRegionSize.width + offsetX + blurRadius + spreadDistance),
|
||||
0,
|
||||
shadowRectSize.width,
|
||||
shadowRectSize.height);
|
||||
CGContextAddRect(context, shadowRect);
|
||||
CGRect shadowCastingRect = CGRectInset(shadowFrame, -blurRadius, -blurRadius);
|
||||
CGRect clearRegionRect = CGRectInset(shadowFrame, spreadDistance, spreadDistance);
|
||||
CGPoint offsetToMoveOffscreen = CGPointMake(
|
||||
-fmax(
|
||||
shadowCastingRect.size.width,
|
||||
clearRegionRect.size.width + offsetX + (clearRegionRect.origin.x - shadowCastingRect.origin.x)) -
|
||||
shadowCastingRect.origin.x,
|
||||
0);
|
||||
shadowCastingRect = CGRectOffset(shadowCastingRect, offsetToMoveOffscreen.x, offsetToMoveOffscreen.y);
|
||||
clearRegionRect =
|
||||
CGRectOffset(clearRegionRect, offsetToMoveOffscreen.x + offsetX, offsetToMoveOffscreen.y + offsetY);
|
||||
CGContextAddRect(context, shadowCastingRect);
|
||||
|
||||
const RCTCornerInsets cornerInsetsForClearRegion =
|
||||
RCTGetCornerInsets(cornerRadiiForBoxShadow(cornerRadii, -spreadDistance), UIEdgeInsetsZero);
|
||||
CGPathRef clearRegionPath = RCTPathCreateWithRoundedRect(
|
||||
CGRectMake(
|
||||
shadowRect.origin.x + offsetX + blurRadius + spreadDistance,
|
||||
shadowRect.origin.y + offsetY + blurRadius + spreadDistance,
|
||||
clearRegionSize.width,
|
||||
clearRegionSize.height),
|
||||
cornerInsetsForClearRegion,
|
||||
nil);
|
||||
RCTGetCornerInsets(cornerRadiiForBoxShadow(cornerRadii, -spreadDistance), edgeInsets);
|
||||
CGPathRef clearRegionPath = RCTPathCreateWithRoundedRect(clearRegionRect, cornerInsetsForClearRegion, nil);
|
||||
CGContextAddPath(context, clearRegionPath);
|
||||
|
||||
// Third, set the shadow graphics state with the appropriate offset such that
|
||||
// it is positioned on top of the view. We subtract blurRadius because the
|
||||
// shadow rect is padded.
|
||||
CGContextSetShadowWithColor(
|
||||
context,
|
||||
CGSizeMake(
|
||||
-shadowRect.origin.x - boundingRect.origin.x - blurRadius,
|
||||
-shadowRect.origin.y - boundingRect.origin.y - blurRadius),
|
||||
blurRadius,
|
||||
color);
|
||||
context, CGSizeMake(-offsetToMoveOffscreen.x, -offsetToMoveOffscreen.y), blurRadius, color);
|
||||
|
||||
// Fourth, the Core Graphics functions to actually draw the shadow rect
|
||||
// and thus the shadow itself. Note we use an EO fill path so that the
|
||||
@@ -259,7 +258,11 @@ static void renderInsetShadows(
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
UIImage *RCTGetBoxShadowImage(std::vector<BoxShadow> shadows, RCTCornerRadii cornerRadii, CALayer *layer)
|
||||
UIImage *RCTGetBoxShadowImage(
|
||||
const std::vector<BoxShadow> &shadows,
|
||||
RCTCornerRadii cornerRadii,
|
||||
UIEdgeInsets edgeInsets,
|
||||
CALayer *layer)
|
||||
{
|
||||
CGRect boundingRect = RCTGetBoundingRect(shadows, layer.bounds.size);
|
||||
UIGraphicsImageRendererFormat *const rendererFormat = [UIGraphicsImageRendererFormat defaultFormat];
|
||||
@@ -274,7 +277,7 @@ UIImage *RCTGetBoxShadowImage(std::vector<BoxShadow> shadows, RCTCornerRadii cor
|
||||
// Inset shadows could draw over those outset shadows but if the shadow
|
||||
// colors have alpha < 1 then we will have inaccurate alpha compositing
|
||||
renderOutsetShadows(outsetShadows, cornerRadii, layer, boundingRect, context);
|
||||
renderInsetShadows(insetShadows, cornerRadii, layer, boundingRect, context);
|
||||
renderInsetShadows(insetShadows, cornerRadii, edgeInsets, layer, boundingRect, context);
|
||||
}];
|
||||
|
||||
return boxShadowImage;
|
||||
|
||||
Reference in New Issue
Block a user