Skip to content

Commit

Permalink
Add support for assymetrical border radii when using % (facebook#46009)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: facebook#46009

as title

Changelog: [Internal]

Reviewed By: NickGerleman

Differential Revision: D61148739

fbshipit-source-id: 7cc37f98ab441df2e0df7cfa0415791cb642caaf
  • Loading branch information
jorge-cab authored and facebook-github-bot committed Aug 23, 2024
1 parent 858ad5e commit 4075418
Show file tree
Hide file tree
Showing 9 changed files with 143 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -624,10 +624,14 @@ - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
static RCTCornerRadii RCTCornerRadiiFromBorderRadii(BorderRadii borderRadii)
{
return RCTCornerRadii{
.topLeft = (CGFloat)borderRadii.topLeft,
.topRight = (CGFloat)borderRadii.topRight,
.bottomLeft = (CGFloat)borderRadii.bottomLeft,
.bottomRight = (CGFloat)borderRadii.bottomRight};
.topLeftHorizontal = (CGFloat)borderRadii.topLeft.horizontal,
.topLeftVertical = (CGFloat)borderRadii.topLeft.vertical,
.topRightHorizontal = (CGFloat)borderRadii.topRight.horizontal,
.topRightVertical = (CGFloat)borderRadii.topRight.vertical,
.bottomLeftHorizontal = (CGFloat)borderRadii.bottomLeft.horizontal,
.bottomLeftVertical = (CGFloat)borderRadii.bottomLeft.vertical,
.bottomRightHorizontal = (CGFloat)borderRadii.bottomRight.horizontal,
.bottomRightVertical = (CGFloat)borderRadii.bottomRight.vertical};
}

static RCTBorderColors RCTCreateRCTBorderColorsFromBorderColors(BorderColors borderColors)
Expand Down Expand Up @@ -748,7 +752,7 @@ - (void)invalidateLayer
CGColorRef borderColor = RCTCreateCGColorRefFromSharedColor(borderMetrics.borderColors.left);
layer.borderColor = borderColor;
CGColorRelease(borderColor);
layer.cornerRadius = (CGFloat)borderMetrics.borderRadii.topLeft;
layer.cornerRadius = (CGFloat)borderMetrics.borderRadii.topLeft.horizontal;

layer.cornerCurve = CornerCurveFromBorderCurve(borderMetrics.borderCurves.topLeft);

Expand Down Expand Up @@ -810,7 +814,7 @@ - (void)invalidateLayer
if (self.clipsToBounds) {
if (borderMetrics.borderRadii.isUniform()) {
// In this case we can simply use `cornerRadius` exclusively.
cornerRadius = borderMetrics.borderRadii.topLeft;
cornerRadius = borderMetrics.borderRadii.topLeft.horizontal;
} else {
RCTCornerInsets cornerInsets =
RCTGetCornerInsets(RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), UIEdgeInsetsZero);
Expand Down Expand Up @@ -856,7 +860,7 @@ - (void)invalidateLayer
alpha:self.layer.opacity]
.CGColor;
if (borderMetrics.borderRadii.isUniform()) {
_filterLayer.cornerRadius = borderMetrics.borderRadii.topLeft;
_filterLayer.cornerRadius = borderMetrics.borderRadii.topLeft.horizontal;
} else {
RCTCornerInsets cornerInsets =
RCTGetCornerInsets(RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), UIEdgeInsetsZero);
Expand Down
12 changes: 8 additions & 4 deletions packages/react-native/React/Fabric/Utils/RCTBoxShadow.mm
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,14 @@ static CGFloat adjustedCornerRadius(CGFloat cornerRadius, CGFloat spreadDistance
static RCTCornerRadii cornerRadiiForBoxShadow(RCTCornerRadii cornerRadii, CGFloat spreadDistance)
{
return {
adjustedCornerRadius(cornerRadii.topLeft, spreadDistance),
adjustedCornerRadius(cornerRadii.topRight, spreadDistance),
adjustedCornerRadius(cornerRadii.bottomLeft, spreadDistance),
adjustedCornerRadius(cornerRadii.bottomRight, spreadDistance)};
adjustedCornerRadius(cornerRadii.topLeftHorizontal, spreadDistance),
adjustedCornerRadius(cornerRadii.topLeftVertical, spreadDistance),
adjustedCornerRadius(cornerRadii.topRightHorizontal, spreadDistance),
adjustedCornerRadius(cornerRadii.topRightVertical, spreadDistance),
adjustedCornerRadius(cornerRadii.bottomLeftHorizontal, spreadDistance),
adjustedCornerRadius(cornerRadii.bottomLeftVertical, spreadDistance),
adjustedCornerRadius(cornerRadii.bottomRightHorizontal, spreadDistance),
adjustedCornerRadius(cornerRadii.bottomRightVertical, spreadDistance)};
}

// Returns the smallest CGRect that will contain all shadows and the layer itself.
Expand Down
14 changes: 9 additions & 5 deletions packages/react-native/React/Views/RCTBorderDrawing.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@
#import <React/RCTDefines.h>

typedef struct {
CGFloat topLeft;
CGFloat topRight;
CGFloat bottomLeft;
CGFloat bottomRight;
CGFloat topLeftHorizontal;
CGFloat topLeftVertical;
CGFloat topRightHorizontal;
CGFloat topRightVertical;
CGFloat bottomLeftHorizontal;
CGFloat bottomLeftVertical;
CGFloat bottomRightHorizontal;
CGFloat bottomRightVertical;
} RCTCornerRadii;

typedef struct {
Expand All @@ -35,7 +39,7 @@ typedef struct {
* Determine if the border widths, colors and radii are all equal.
*/
RCT_EXTERN BOOL RCTBorderInsetsAreEqual(UIEdgeInsets borderInsets);
RCT_EXTERN BOOL RCTCornerRadiiAreEqual(RCTCornerRadii cornerRadii);
RCT_EXTERN BOOL RCTCornerRadiiAreEqualAndSymmetrical(RCTCornerRadii cornerRadii);
RCT_EXTERN BOOL RCTBorderColorsAreEqual(RCTBorderColors borderColors);

/**
Expand Down
37 changes: 23 additions & 14 deletions packages/react-native/React/Views/RCTBorderDrawing.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@ BOOL RCTBorderInsetsAreEqual(UIEdgeInsets borderInsets)
ABS(borderInsets.left - borderInsets.top) < RCTViewBorderThreshold;
}

BOOL RCTCornerRadiiAreEqual(RCTCornerRadii cornerRadii)
BOOL RCTCornerRadiiAreEqualAndSymmetrical(RCTCornerRadii cornerRadii)
{
return ABS(cornerRadii.topLeft - cornerRadii.topRight) < RCTViewBorderThreshold &&
ABS(cornerRadii.topLeft - cornerRadii.bottomLeft) < RCTViewBorderThreshold &&
ABS(cornerRadii.topLeft - cornerRadii.bottomRight) < RCTViewBorderThreshold;
return cornerRadii.topLeftHorizontal == cornerRadii.topLeftHorizontal &&
cornerRadii.topRightHorizontal == cornerRadii.topRightVertical &&
cornerRadii.bottomLeftHorizontal == cornerRadii.bottomLeftVertical &&
cornerRadii.bottomRightHorizontal == cornerRadii.bottomRightVertical &&
ABS(cornerRadii.topLeftHorizontal - cornerRadii.topRightHorizontal) < RCTViewBorderThreshold &&
ABS(cornerRadii.topLeftHorizontal - cornerRadii.bottomLeftHorizontal) < RCTViewBorderThreshold &&
ABS(cornerRadii.topLeftHorizontal - cornerRadii.bottomRightHorizontal) < RCTViewBorderThreshold;
}

BOOL RCTBorderColorsAreEqual(RCTBorderColors borderColors)
Expand All @@ -35,20 +39,20 @@ RCTCornerInsets RCTGetCornerInsets(RCTCornerRadii cornerRadii, UIEdgeInsets edge
{
return (RCTCornerInsets){
{
MAX(0, cornerRadii.topLeft - edgeInsets.left),
MAX(0, cornerRadii.topLeft - edgeInsets.top),
MAX(0, cornerRadii.topLeftHorizontal - edgeInsets.left),
MAX(0, cornerRadii.topLeftVertical - edgeInsets.top),
},
{
MAX(0, cornerRadii.topRight - edgeInsets.right),
MAX(0, cornerRadii.topRight - edgeInsets.top),
MAX(0, cornerRadii.topRightHorizontal - edgeInsets.right),
MAX(0, cornerRadii.topRightVertical - edgeInsets.top),
},
{
MAX(0, cornerRadii.bottomLeft - edgeInsets.left),
MAX(0, cornerRadii.bottomLeft - edgeInsets.bottom),
MAX(0, cornerRadii.bottomLeftHorizontal - edgeInsets.left),
MAX(0, cornerRadii.bottomLeftVertical - edgeInsets.bottom),
},
{
MAX(0, cornerRadii.bottomRight - edgeInsets.right),
MAX(0, cornerRadii.bottomRight - edgeInsets.bottom),
MAX(0, cornerRadii.bottomRightHorizontal - edgeInsets.right),
MAX(0, cornerRadii.bottomRightVertical - edgeInsets.bottom),
}};
}

Expand Down Expand Up @@ -159,8 +163,13 @@ CGPathRef RCTPathCreateWithRoundedRect(CGRect bounds, RCTCornerInsets cornerInse
NS_INLINE BOOL RCTCornerRadiiAreAboveThreshold(RCTCornerRadii cornerRadii)
{
return (
cornerRadii.topLeft > RCTViewBorderThreshold || cornerRadii.topRight > RCTViewBorderThreshold ||
cornerRadii.bottomLeft > RCTViewBorderThreshold || cornerRadii.bottomRight > RCTViewBorderThreshold);
cornerRadii.topLeftHorizontal > RCTViewBorderThreshold || cornerRadii.topLeftVertical > RCTViewBorderThreshold ||
cornerRadii.topRightHorizontal > RCTViewBorderThreshold ||
cornerRadii.topRightVertical > RCTViewBorderThreshold ||
cornerRadii.bottomLeftHorizontal > RCTViewBorderThreshold ||
cornerRadii.bottomLeftVertical > RCTViewBorderThreshold ||
cornerRadii.bottomRightHorizontal > RCTViewBorderThreshold ||
cornerRadii.bottomRightVertical > RCTViewBorderThreshold);
}

static CGPathRef RCTPathCreateOuterOutline(BOOL drawToEdge, CGRect rect, RCTCornerRadii cornerRadii)
Expand Down
10 changes: 5 additions & 5 deletions packages/react-native/React/Views/RCTView.m
Original file line number Diff line number Diff line change
Expand Up @@ -803,8 +803,8 @@ - (void)displayLayer:(CALayer *)layer
const UIEdgeInsets borderInsets = [self bordersAsInsets];
const RCTBorderColors borderColors = [self borderColorsWithTraitCollection:self.traitCollection];

BOOL useIOSBorderRendering = RCTCornerRadiiAreEqual(cornerRadii) && RCTBorderInsetsAreEqual(borderInsets) &&
RCTBorderColorsAreEqual(borderColors) &&
BOOL useIOSBorderRendering = RCTCornerRadiiAreEqualAndSymmetrical(cornerRadii) &&
RCTBorderInsetsAreEqual(borderInsets) && RCTBorderColorsAreEqual(borderColors) &&

// iOS draws borders in front of the content whereas CSS draws them behind
// the content. For this reason, only use iOS border drawing when clipping
Expand All @@ -821,7 +821,7 @@ - (void)displayLayer:(CALayer *)layer
backgroundColor = [_backgroundColor resolvedColorWithTraitCollection:self.traitCollection].CGColor;

if (useIOSBorderRendering) {
layer.cornerRadius = cornerRadii.topLeft;
layer.cornerRadius = cornerRadii.topLeftHorizontal;
layer.borderColor = borderColors.left;
layer.borderWidth = borderInsets.left;
layer.backgroundColor = backgroundColor;
Expand Down Expand Up @@ -928,8 +928,8 @@ - (void)updateClippingForLayer:(CALayer *)layer

if (self.clipsToBounds) {
const RCTCornerRadii cornerRadii = [self cornerRadii];
if (RCTCornerRadiiAreEqual(cornerRadii)) {
cornerRadius = cornerRadii.topLeft;
if (RCTCornerRadiiAreEqualAndSymmetrical(cornerRadii)) {
cornerRadius = cornerRadii.topLeftHorizontal;

} else {
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -388,89 +388,89 @@ static BorderRadii ensureNoOverlap(const BorderRadii& radii, const Size& size) {
// Source: https://www.w3.org/TR/css-backgrounds-3/#corner-overlap

auto insets = EdgeInsets{
/* .left = */ radii.topLeft + radii.bottomLeft,
/* .top = */ radii.topLeft + radii.topRight,
/* .right = */ radii.topRight + radii.bottomRight,
/* .bottom = */ radii.bottomLeft + radii.bottomRight,
.left = radii.topLeft.horizontal + radii.bottomLeft.horizontal,
.top = radii.topLeft.vertical + radii.topRight.vertical,
.right = radii.topRight.horizontal + radii.bottomRight.horizontal,
.bottom = radii.bottomLeft.vertical + radii.bottomRight.vertical,
};

auto insetsScale = EdgeInsets{
/* .left = */
insets.left > 0 ? std::min((Float)1.0, size.height / insets.left) : 0,
/* .top = */
insets.top > 0 ? std::min((Float)1.0, size.width / insets.top) : 0,
/* .right = */
insets.right > 0 ? std::min((Float)1.0, size.height / insets.right) : 0,
/* .bottom = */
insets.bottom > 0 ? std::min((Float)1.0, size.width / insets.bottom) : 0,
.left =
insets.left > 0 ? std::min((Float)1.0, size.height / insets.left) : 0,
.top = insets.top > 0 ? std::min((Float)1.0, size.width / insets.top) : 0,
.right = insets.right > 0
? std::min((Float)1.0, size.height / insets.right)
: 0,
.bottom = insets.bottom > 0
? std::min((Float)1.0, size.width / insets.bottom)
: 0,
};

return BorderRadii{
/* topLeft = */
static_cast<float>(
radii.topLeft * std::min(insetsScale.top, insetsScale.left)),
/* topRight = */
static_cast<float>(
radii.topRight * std::min(insetsScale.top, insetsScale.right)),
/* bottomLeft = */
static_cast<float>(
radii.bottomLeft * std::min(insetsScale.bottom, insetsScale.left)),
/* bottomRight = */
static_cast<float>(
radii.bottomRight * std::min(insetsScale.bottom, insetsScale.right)),
.topLeft =
{static_cast<float>(
radii.topLeft.horizontal *
std::min(insetsScale.top, insetsScale.left)),
static_cast<float>(
radii.topLeft.vertical *
std::min(insetsScale.top, insetsScale.left))},
.topRight =
{static_cast<float>(
radii.topRight.horizontal *
std::min(insetsScale.top, insetsScale.right)),
static_cast<float>(
radii.topRight.vertical *
std::min(insetsScale.top, insetsScale.right))},
.bottomLeft =
{static_cast<float>(
radii.bottomLeft.horizontal *
std::min(insetsScale.bottom, insetsScale.left)),
static_cast<float>(
radii.bottomLeft.vertical *
std::min(insetsScale.bottom, insetsScale.left))},
.bottomRight =
{static_cast<float>(
radii.bottomRight.horizontal *
std::min(insetsScale.bottom, insetsScale.right)),
static_cast<float>(
radii.bottomRight.vertical *
std::min(insetsScale.bottom, insetsScale.right))},
};
}

static BorderRadii radiiPercentToPoint(
const RectangleCorners<ValueUnit>& radii,
const Size& size) {
return BorderRadii{
/* topLeft = */
(radii.topLeft.unit == UnitType::Percent)
? static_cast<float>(
(radii.topLeft.value / 100) * std::max(size.width, size.height))
: static_cast<float>(radii.topLeft.value),
/* topRight = */
(radii.topRight.unit == UnitType::Percent)
? static_cast<float>(
(radii.topRight.value / 100) *
std::max(size.width, size.height))
: static_cast<float>(radii.topRight.value),
/* bottomLeft = */
(radii.bottomLeft.unit == UnitType::Percent)
? static_cast<float>(
(radii.bottomLeft.value / 100) *
std::max(size.width, size.height))
: static_cast<float>(radii.bottomLeft.value),
/* bottomRight = */
(radii.bottomRight.unit == UnitType::Percent)
? static_cast<float>(
(radii.bottomRight.value / 100) *
std::max(size.width, size.height))
: static_cast<float>(radii.bottomRight.value),
.topLeft =
{radii.topLeft.resolve(size.width),
radii.topLeft.resolve(size.height)},
.topRight =
{radii.topRight.resolve(size.width),
radii.topRight.resolve(size.height)},
.bottomLeft =
{radii.bottomLeft.resolve(size.width),
radii.bottomLeft.resolve(size.height)},
.bottomRight =
{radii.bottomRight.resolve(size.width),
radii.bottomRight.resolve(size.height)},
};
}

CascadedBorderWidths BaseViewProps::getBorderWidths() const {
return CascadedBorderWidths{
/* .left = */ optionalFloatFromYogaValue(
yogaStyle.border(yoga::Edge::Left)),
/* .top = */
optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Top)),
/* .right = */
optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Right)),
/* .bottom = */
optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Bottom)),
/* .start = */
optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Start)),
/* .end = */
optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::End)),
/* .horizontal = */
optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Horizontal)),
/* .vertical = */
optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Vertical)),
/* .all = */
optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::All)),
.left = optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Left)),
.top = optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Top)),
.right = optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Right)),
.bottom =
optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Bottom)),
.start = optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Start)),
.end = optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::End)),
.horizontal =
optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Horizontal)),
.vertical =
optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::Vertical)),
.all = optionalFloatFromYogaValue(yogaStyle.border(yoga::Edge::All)),
};
}

Expand All @@ -486,13 +486,11 @@ BorderMetrics BaseViewProps::resolveBorderMetrics(
layoutMetrics.frame.size);

return {
/* .borderColors = */ borderColors.resolve(isRTL, {}),
/* .borderWidths = */ borderWidths.resolve(isRTL, 0),
/* .borderRadii = */
ensureNoOverlap(radii, layoutMetrics.frame.size),
/* .borderCurves = */
borderCurves.resolve(isRTL, BorderCurve::Circular),
/* .borderStyles = */ borderStyles.resolve(isRTL, BorderStyle::Solid),
.borderColors = borderColors.resolve(isRTL, {}),
.borderWidths = borderWidths.resolve(isRTL, 0),
.borderRadii = ensureNoOverlap(radii, layoutMetrics.frame.size),
.borderCurves = borderCurves.resolve(isRTL, BorderCurve::Circular),
.borderStyles = borderStyles.resolve(isRTL, BorderStyle::Solid),
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ enum class BorderCurve : uint8_t { Circular, Continuous };

enum class BorderStyle : uint8_t { Solid, Dotted, Dashed };

struct CornerRadii {
float vertical{0.0f};
float horizontal{0.0f};

bool operator==(const CornerRadii& other) const = default;
};

enum class Cursor : uint8_t {
Auto,
Alias,
Expand Down Expand Up @@ -289,7 +296,7 @@ using BorderWidths = RectangleEdges<Float>;
using BorderCurves = RectangleCorners<BorderCurve>;
using BorderStyles = RectangleEdges<BorderStyle>;
using BorderColors = RectangleEdges<SharedColor>;
using BorderRadii = RectangleCorners<Float>;
using BorderRadii = RectangleCorners<CornerRadii>;

using CascadedBorderWidths = CascadedRectangleEdges<Float>;
using CascadedBorderCurves = CascadedRectangleCorners<BorderCurve>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ struct ValueUnit {
return !(*this == other);
}

constexpr float resolve(float referenceLength) {
constexpr float resolve(float referenceLength) const {
switch (unit) {
case UnitType::Point:
return value;
Expand Down
Loading

0 comments on commit 4075418

Please sign in to comment.