Fabric: Fixed overlapping border radii
Summary: UAs must adjust border radius values to fit a content box: >>> Corner curves must not overlap: When the sum of any two adjacent border radii exceeds the size of the border box, UAs must proportionally reduce the used values of all border radii until none of them overlap. This diff implements that. Reviewed By: mdvacca Differential Revision: D15028325 fbshipit-source-id: 368232ffa2fa0409d13759bbbe7fe10f8474c400
This commit is contained in:
Родитель
2688780394
Коммит
b4044e5b1b
|
@ -27,7 +27,7 @@ using namespace facebook::react;
|
|||
- (instancetype)initWithFrame:(CGRect)frame
|
||||
{
|
||||
if (self = [super initWithFrame:frame]) {
|
||||
static const auto defaultProps = std::make_shared<const ViewProps>();
|
||||
static auto const defaultProps = std::make_shared<ViewProps const>();
|
||||
_props = defaultProps;
|
||||
}
|
||||
return self;
|
||||
|
@ -101,16 +101,16 @@ using namespace facebook::react;
|
|||
RCTAssert(
|
||||
propsRawPtr &&
|
||||
([self class] == [RCTViewComponentView class] ||
|
||||
typeid(*propsRawPtr).hash_code() != typeid(const ViewProps).hash_code()),
|
||||
typeid(*propsRawPtr).hash_code() != typeid(ViewProps const).hash_code()),
|
||||
@"`RCTViewComponentView` subclasses (and `%@` particularly) must setup `_props`"
|
||||
" instance variable with a default value in the constructor.",
|
||||
NSStringFromClass([self class]));
|
||||
#endif
|
||||
|
||||
const auto &oldViewProps = *std::static_pointer_cast<const ViewProps>(oldProps ?: _props);
|
||||
const auto &newViewProps = *std::static_pointer_cast<const ViewProps>(props);
|
||||
auto const &oldViewProps = *std::static_pointer_cast<ViewProps const>(oldProps ?: _props);
|
||||
auto const &newViewProps = *std::static_pointer_cast<ViewProps const>(props);
|
||||
|
||||
_props = std::static_pointer_cast<const ViewProps>(props);
|
||||
_props = std::static_pointer_cast<ViewProps const>(props);
|
||||
|
||||
BOOL needsInvalidateLayer = NO;
|
||||
|
||||
|
@ -251,8 +251,8 @@ using namespace facebook::react;
|
|||
|
||||
- (void)updateEventEmitter:(SharedEventEmitter)eventEmitter
|
||||
{
|
||||
assert(std::dynamic_pointer_cast<const ViewEventEmitter>(eventEmitter));
|
||||
_eventEmitter = std::static_pointer_cast<const ViewEventEmitter>(eventEmitter);
|
||||
assert(std::dynamic_pointer_cast<ViewEventEmitter const>(eventEmitter));
|
||||
_eventEmitter = std::static_pointer_cast<ViewEventEmitter const>(eventEmitter);
|
||||
}
|
||||
|
||||
- (void)updateLayoutMetrics:(LayoutMetrics)layoutMetrics oldLayoutMetrics:(LayoutMetrics)oldLayoutMetrics
|
||||
|
@ -374,15 +374,14 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle)
|
|||
return;
|
||||
}
|
||||
|
||||
const auto borderMetrics =
|
||||
_props->resolveBorderMetrics(_layoutMetrics.layoutDirection == LayoutDirection::RightToLeft);
|
||||
auto const borderMetrics = _props->resolveBorderMetrics(_layoutMetrics);
|
||||
|
||||
// Stage 1. Shadow Path
|
||||
BOOL layerHasShadow = layer.shadowOpacity > 0 && CGColorGetAlpha(layer.shadowColor) > 0;
|
||||
BOOL const layerHasShadow = layer.shadowOpacity > 0 && CGColorGetAlpha(layer.shadowColor) > 0;
|
||||
if (layerHasShadow) {
|
||||
if (CGColorGetAlpha(_backgroundColor.CGColor) > 0.999) {
|
||||
// If view has a solid background color, calculate shadow path from border.
|
||||
const RCTCornerInsets cornerInsets =
|
||||
RCTCornerInsets const cornerInsets =
|
||||
RCTGetCornerInsets(RCTCornerRadiiFromBorderRadii(borderMetrics.borderRadii), UIEdgeInsetsZero);
|
||||
CGPathRef shadowPath = RCTPathCreateWithRoundedRect(self.bounds, cornerInsets, nil);
|
||||
layer.shadowPath = shadowPath;
|
||||
|
@ -396,7 +395,7 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle)
|
|||
}
|
||||
|
||||
// Stage 2. Border Rendering
|
||||
const bool useCoreAnimationBorderRendering =
|
||||
bool const useCoreAnimationBorderRendering =
|
||||
borderMetrics.borderColors.isUniform() && borderMetrics.borderWidths.isUniform() &&
|
||||
borderMetrics.borderStyles.isUniform() && borderMetrics.borderRadii.isUniform() &&
|
||||
borderMetrics.borderStyles.left == BorderStyle::Solid &&
|
||||
|
@ -458,7 +457,7 @@ static RCTBorderStyle RCTBorderStyleFromBorderStyle(BorderStyle borderStyle)
|
|||
_borderLayer.contents = (id)image.CGImage;
|
||||
_borderLayer.contentsScale = image.scale;
|
||||
|
||||
const BOOL isResizable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero);
|
||||
BOOL isResizable = !UIEdgeInsetsEqualToEdgeInsets(image.capInsets, UIEdgeInsetsZero);
|
||||
if (isResizable) {
|
||||
_borderLayer.contentsCenter = contentsCenter;
|
||||
} else {
|
||||
|
@ -530,14 +529,14 @@ static NSString *RCTRecursiveAccessibilityLabel(UIView *view)
|
|||
|
||||
- (NSArray<UIAccessibilityCustomAction *> *)accessibilityCustomActions
|
||||
{
|
||||
const auto &accessibilityActions = _props->accessibilityActions;
|
||||
auto const &accessibilityActions = _props->accessibilityActions;
|
||||
|
||||
if (accessibilityActions.size() == 0) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSMutableArray<UIAccessibilityCustomAction *> *customActions = [NSMutableArray array];
|
||||
for (const auto &accessibilityAction : accessibilityActions) {
|
||||
for (auto const &accessibilityAction : accessibilityActions) {
|
||||
[customActions
|
||||
addObject:[[UIAccessibilityCustomAction alloc] initWithName:RCTNSStringFromString(accessibilityAction)
|
||||
target:self
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
#include "ViewProps.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include <react/components/view/conversions.h>
|
||||
#include <react/components/view/propsConversions.h>
|
||||
#include <react/core/propsConversions.h>
|
||||
|
@ -16,9 +18,9 @@
|
|||
namespace facebook {
|
||||
namespace react {
|
||||
|
||||
ViewProps::ViewProps(const YGStyle &yogaStyle) : YogaStylableProps(yogaStyle) {}
|
||||
ViewProps::ViewProps(YGStyle const &yogaStyle) : YogaStylableProps(yogaStyle) {}
|
||||
|
||||
ViewProps::ViewProps(const ViewProps &sourceProps, const RawProps &rawProps)
|
||||
ViewProps::ViewProps(ViewProps const &sourceProps, RawProps const &rawProps)
|
||||
: Props(sourceProps, rawProps),
|
||||
YogaStylableProps(sourceProps, rawProps),
|
||||
AccessibilityProps(sourceProps, rawProps),
|
||||
|
@ -77,7 +79,47 @@ ViewProps::ViewProps(const ViewProps &sourceProps, const RawProps &rawProps)
|
|||
|
||||
#pragma mark - Convenience Methods
|
||||
|
||||
BorderMetrics ViewProps::resolveBorderMetrics(bool isRTL) const {
|
||||
static BorderRadii ensureNoOverlap(BorderRadii const &radii, Size const &size) {
|
||||
// "Corner curves must not overlap: When the sum of any two adjacent border
|
||||
// radii exceeds the size of the border box, UAs must proportionally reduce
|
||||
// the used values of all border radii until none of them overlap."
|
||||
// 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,
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
return BorderRadii{
|
||||
/* topLeft = */
|
||||
radii.topLeft * std::min(insetsScale.top, insetsScale.left),
|
||||
/* topRight = */
|
||||
radii.topRight * std::min(insetsScale.top, insetsScale.right),
|
||||
/* bottomLeft = */
|
||||
radii.bottomLeft * std::min(insetsScale.bottom, insetsScale.left),
|
||||
/* bottomRight = */
|
||||
radii.bottomRight * std::min(insetsScale.bottom, insetsScale.right),
|
||||
};
|
||||
}
|
||||
|
||||
BorderMetrics ViewProps::resolveBorderMetrics(
|
||||
LayoutMetrics const &layoutMetrics) const {
|
||||
auto isRTL =
|
||||
bool{layoutMetrics.layoutDirection == LayoutDirection::RightToLeft};
|
||||
|
||||
auto borderWidths = CascadedBorderWidths{
|
||||
/* .left = */ optionalFloatFromYogaValue(yogaStyle.border[YGEdgeLeft]),
|
||||
/* .top = */ optionalFloatFromYogaValue(yogaStyle.border[YGEdgeTop]),
|
||||
|
@ -96,7 +138,8 @@ BorderMetrics ViewProps::resolveBorderMetrics(bool isRTL) const {
|
|||
return {
|
||||
/* .borderColors = */ borderColors.resolve(isRTL, {}),
|
||||
/* .borderWidths = */ borderWidths.resolve(isRTL, 0),
|
||||
/* .borderRadii = */ borderRadii.resolve(isRTL, 0),
|
||||
/* .borderRadii = */
|
||||
ensureNoOverlap(borderRadii.resolve(isRTL, 0), layoutMetrics.frame.size),
|
||||
/* .borderStyles = */ borderStyles.resolve(isRTL, BorderStyle::Solid),
|
||||
};
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <react/components/view/AccessibilityProps.h>
|
||||
#include <react/components/view/YogaStylableProps.h>
|
||||
#include <react/components/view/primitives.h>
|
||||
#include <react/core/LayoutMetrics.h>
|
||||
#include <react/core/Props.h>
|
||||
#include <react/graphics/Color.h>
|
||||
#include <react/graphics/Geometry.h>
|
||||
|
@ -20,50 +21,50 @@ namespace react {
|
|||
|
||||
class ViewProps;
|
||||
|
||||
using SharedViewProps = std::shared_ptr<const ViewProps>;
|
||||
using SharedViewProps = std::shared_ptr<ViewProps const>;
|
||||
|
||||
class ViewProps : public Props,
|
||||
public YogaStylableProps,
|
||||
public AccessibilityProps {
|
||||
public:
|
||||
ViewProps() = default;
|
||||
ViewProps(const YGStyle &yogaStyle);
|
||||
ViewProps(const ViewProps &sourceProps, const RawProps &rawProps);
|
||||
ViewProps(YGStyle const &yogaStyle);
|
||||
ViewProps(ViewProps const &sourceProps, RawProps const &rawProps);
|
||||
|
||||
#pragma mark - Props
|
||||
|
||||
// Color
|
||||
const Float opacity{1.0};
|
||||
const SharedColor foregroundColor{};
|
||||
const SharedColor backgroundColor{};
|
||||
Float const opacity{1.0};
|
||||
SharedColor const foregroundColor{};
|
||||
SharedColor const backgroundColor{};
|
||||
|
||||
// Borders
|
||||
const CascadedBorderRadii borderRadii{};
|
||||
const CascadedBorderColors borderColors{};
|
||||
const CascadedBorderStyles borderStyles{};
|
||||
CascadedBorderRadii const borderRadii{};
|
||||
CascadedBorderColors const borderColors{};
|
||||
CascadedBorderStyles const borderStyles{};
|
||||
|
||||
// Shadow
|
||||
const SharedColor shadowColor{};
|
||||
const Size shadowOffset{};
|
||||
const Float shadowOpacity{};
|
||||
const Float shadowRadius{};
|
||||
SharedColor const shadowColor{};
|
||||
Size const shadowOffset{};
|
||||
Float const shadowOpacity{};
|
||||
Float const shadowRadius{};
|
||||
|
||||
// Transform
|
||||
const Transform transform{};
|
||||
const bool backfaceVisibility{};
|
||||
const bool shouldRasterize{};
|
||||
const int zIndex{};
|
||||
Transform transform{};
|
||||
bool const backfaceVisibility{};
|
||||
bool const shouldRasterize{};
|
||||
int const zIndex{};
|
||||
|
||||
// Events
|
||||
const PointerEventsMode pointerEvents{};
|
||||
const EdgeInsets hitSlop{};
|
||||
const bool onLayout{};
|
||||
PointerEventsMode const pointerEvents{};
|
||||
EdgeInsets const hitSlop{};
|
||||
bool const onLayout{};
|
||||
|
||||
const bool collapsable{true};
|
||||
bool const collapsable{true};
|
||||
|
||||
#pragma mark - Convenience Methods
|
||||
|
||||
BorderMetrics resolveBorderMetrics(bool isRTL) const;
|
||||
BorderMetrics resolveBorderMetrics(LayoutMetrics const &layoutMetrics) const;
|
||||
|
||||
#pragma mark - DebugStringConvertible
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче