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:
Valentin Shergin 2019-04-22 09:01:55 -07:00 коммит произвёл Facebook Github Bot
Родитель 2688780394
Коммит b4044e5b1b
3 изменённых файлов: 84 добавлений и 41 удалений

Просмотреть файл

@ -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