Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for assymetrical border radii when using % #46009

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading