Fabric: Proper way to represent (and parse) borders in ViewProps

Summary:
@public
Previously, ViewProps class coundn't represent whole spectre of possible values of border metrics (e.g. the border color was unified).
Now it's complete and direction-specific.

Reviewed By: sahrens

Differential Revision: D9628361

fbshipit-source-id: 6d3b3d4d7e3008e2168cbca732ff99fe5ea595e8
This commit is contained in:
Valentin Shergin 2018-09-07 11:12:11 -07:00 committed by Facebook Github Bot
parent 6e7ffff4a5
commit ca1e9032a4
5 changed files with 202 additions and 25 deletions

View File

@ -137,10 +137,10 @@ using namespace facebook::react;
// `border`
if (
oldViewProps.borderWidth != newViewProps.borderWidth ||
oldViewProps.borderStyle != newViewProps.borderStyle ||
oldViewProps.borderRadius != newViewProps.borderRadius ||
oldViewProps.borderColor != newViewProps.borderColor
oldViewProps.borderWidths != newViewProps.borderWidths ||
oldViewProps.borderStyles != newViewProps.borderStyles ||
oldViewProps.borderRadii != newViewProps.borderRadii ||
oldViewProps.borderColors != newViewProps.borderColors
) {
[self invalidateBorder];
}
@ -174,10 +174,15 @@ using namespace facebook::react;
{
const auto &props = *std::dynamic_pointer_cast<const ViewProps>(_props);
bool useCoreAnimationBorderRendering =
props.borderStyle == BorderStyle::Solid &&
props.borderWidth.isUniform() &&
props.borderRadius.isUniform();
const auto borderMetrics =
props.resolveBorderMetrics(_layoutMetrics.layoutDirection == LayoutDirection::RightToLeft);
const bool useCoreAnimationBorderRendering =
borderMetrics.borderColors.isUniform() &&
borderMetrics.borderWidths.isUniform() &&
borderMetrics.borderStyles.isUniform() &&
borderMetrics.borderRadii.isUniform() &&
borderMetrics.borderStyles.left == BorderStyle::Solid;
CALayer *layer = self.layer;
if (_isCoreAnimationBorderRenderingEnabled != useCoreAnimationBorderRendering) {
@ -190,11 +195,10 @@ using namespace facebook::react;
}
if (useCoreAnimationBorderRendering) {
layer.borderWidth = (CGFloat)props.borderWidth.left;
layer.borderColor = RCTCGColorRefFromSharedColor(props.borderColor);
layer.cornerRadius = (CGFloat)props.borderRadius.topLeft;
_contentView.layer.cornerRadius = (CGFloat)props.borderRadius.topLeft;
_contentView.layer.masksToBounds = YES;
layer.borderWidth = (CGFloat)borderMetrics.borderWidths.left;
layer.borderColor = RCTCGColorRefFromSharedColor(borderMetrics.borderColors.left);
layer.cornerRadius = (CGFloat)borderMetrics.borderRadii.topLeft;
_contentView.layer.cornerRadius = (CGFloat)borderMetrics.borderRadii.topLeft;
} else {
// Not supported yet.
}

View File

@ -8,6 +8,7 @@
#include "ViewProps.h"
#include <fabric/components/view/conversions.h>
#include <fabric/components/view/propsConversions.h>
#include <fabric/core/propsConversions.h>
#include <fabric/debug/debugStringConvertibleUtils.h>
#include <fabric/graphics/conversions.h>
@ -24,10 +25,10 @@ ViewProps::ViewProps(const ViewProps &sourceProps, const RawProps &rawProps):
opacity(convertRawProp(rawProps, "opacity", sourceProps.opacity, (Float)1.0)),
foregroundColor(convertRawProp(rawProps, "foregroundColor", sourceProps.foregroundColor)),
backgroundColor(convertRawProp(rawProps, "backgroundColor", sourceProps.backgroundColor)),
borderWidth(convertRawProp(rawProps, "borderWidth", sourceProps.borderWidth)),
borderRadius(convertRawProp(rawProps, "borderRadius", sourceProps.borderRadius)),
borderColor(convertRawProp(rawProps, "borderColor", sourceProps.borderColor)),
borderStyle(convertRawProp(rawProps, "borderStyle", sourceProps.borderStyle)),
borderWidths(convertRawProp(rawProps, "border", "Width", sourceProps.borderWidths)),
borderRadii(convertRawProp(rawProps, "border", "Radius", sourceProps.borderRadii)),
borderColors(convertRawProp(rawProps, "border", "Color", sourceProps.borderColors)),
borderStyles(convertRawProp(rawProps, "border", "Style", sourceProps.borderStyles)),
shadowColor(convertRawProp(rawProps, "shadowColor", sourceProps.shadowColor)),
shadowOffset(convertRawProp(rawProps, "shadowOffset", sourceProps.shadowOffset)),
shadowOpacity(convertRawProp(rawProps, "shadowOpacity", sourceProps.shadowOpacity)),
@ -40,6 +41,17 @@ ViewProps::ViewProps(const ViewProps &sourceProps, const RawProps &rawProps):
hitSlop(convertRawProp(rawProps, "hitSlop", sourceProps.hitSlop)),
onLayout(convertRawProp(rawProps, "onLayout", sourceProps.onLayout)) {};
#pragma mark - Convenience Methods
BorderMetrics ViewProps::resolveBorderMetrics(bool isRTL) const {
return {
.borderColors = borderColors.resolve(isRTL, {}),
.borderWidths = borderWidths.resolve(isRTL, 0),
.borderRadii = borderRadii.resolve(isRTL, 0),
.borderStyles = borderStyles.resolve(isRTL, BorderStyle::Solid)
};
}
#pragma mark - DebugStringConvertible
SharedDebugStringConvertibleList ViewProps::getDebugProps() const {

View File

@ -39,10 +39,10 @@ public:
const SharedColor backgroundColor {};
// Borders
const EdgeInsets borderWidth {};
const CornerInsets borderRadius {};
const SharedColor borderColor {};
const BorderStyle borderStyle {};
const CascadedBorderWidths borderWidths {};
const CascadedBorderRadii borderRadii {};
const CascadedBorderColors borderColors {};
const CascadedBorderStyles borderStyles {};
// Shadow
const SharedColor shadowColor {};
@ -61,6 +61,10 @@ public:
const EdgeInsets hitSlop {};
const bool onLayout {};
#pragma mark - Convenience Methods
BorderMetrics resolveBorderMetrics(bool isRTL) const;
#pragma mark - DebugStringConvertible
SharedDebugStringConvertibleList getDebugProps() const override;

View File

@ -8,7 +8,9 @@
#pragma once
#include <cmath>
#include <fabric/graphics/Color.h>
#include <fabric/graphics/Geometry.h>
#include <folly/Optional.h>
namespace facebook {
namespace react {
@ -157,5 +159,112 @@ enum class BorderStyle {
Dashed
};
template <typename T>
struct CascadedRectangleEdges {
using Counterpart = RectangleEdges<T>;
using OptionalT = folly::Optional<T>;
OptionalT left {};
OptionalT top {};
OptionalT right {};
OptionalT bottom {};
OptionalT start {};
OptionalT end {};
OptionalT horizontal {};
OptionalT vertical {};
OptionalT all {};
Counterpart resolve(bool isRTL, T defaults) const {
const auto leading = isRTL ? end : start;
const auto trailing = isRTL ? start : end;
const auto horizontalOrAllOrDefault = horizontal.value_or(all.value_or(defaults));
const auto verticalOrAllOrDefault = vertical.value_or(all.value_or(defaults));
return Counterpart {
.left = left.value_or(leading.value_or(horizontalOrAllOrDefault)),
.right = right.value_or(trailing.value_or(horizontalOrAllOrDefault)),
.top = top.value_or(verticalOrAllOrDefault),
.bottom = bottom.value_or(verticalOrAllOrDefault)
};
}
bool operator==(const CascadedRectangleEdges<T> &rhs) const {
return
std::tie(this->left, this->top, this->right, this->bottom, this->start, this->end, this->horizontal, this->vertical, this->all) ==
std::tie(rhs.left, rhs.top, rhs.right, rhs.bottom, rhs.start, rhs.end, rhs.horizontal, rhs.vertical, rhs.all);
}
bool operator!=(const CascadedRectangleEdges<T> &rhs) const {
return !(*this == rhs);
}
};
template <typename T>
struct CascadedRectangleCorners {
using Counterpart = RectangleCorners<T>;
using OptionalT = folly::Optional<T>;
OptionalT topLeft {};
OptionalT topRight {};
OptionalT bottomLeft {};
OptionalT bottomRight {};
OptionalT topStart {};
OptionalT topEnd {};
OptionalT bottomStart {};
OptionalT bottomEnd {};
OptionalT all {};
Counterpart resolve(bool isRTL, T defaults) const {
const auto topLeading = isRTL ? topEnd : topStart;
const auto topTrailing = isRTL ? topStart : topEnd;
const auto bottomLeading = isRTL ? bottomEnd : bottomStart;
const auto bottomTrailing = isRTL ? bottomStart : bottomEnd;
return Counterpart {
.topLeft = topLeft.value_or(topLeading.value_or(all.value_or(defaults))),
.topRight = topRight.value_or(topTrailing.value_or(all.value_or(defaults))),
.bottomLeft = bottomLeft.value_or(topLeading.value_or(all.value_or(defaults))),
.bottomRight = bottomRight.value_or(topTrailing.value_or(all.value_or(defaults)))
};
}
bool operator==(const CascadedRectangleCorners<T> &rhs) const {
return
std::tie(this->topLeft, this->topRight, this->bottomLeft, this->bottomRight, this->topStart, this->topEnd, this->bottomStart, this->bottomEnd, this->all) ==
std::tie(rhs.topLeft, rhs.topRight, rhs.bottomLeft, rhs.bottomRight, rhs.topStart, rhs.topEnd, rhs.bottomStart, rhs.bottomEnd, rhs.all);
}
bool operator!=(const CascadedRectangleCorners<T> &rhs) const {
return !(*this == rhs);
}
};
using BorderWidths = RectangleEdges<Float>;
using BorderStyles = RectangleEdges<BorderStyle>;
using BorderColors = RectangleEdges<SharedColor>;
using BorderRadii = RectangleCorners<Float>;
using CascadedBorderWidths = CascadedRectangleEdges<Float>;
using CascadedBorderStyles = CascadedRectangleEdges<BorderStyle>;
using CascadedBorderColors = CascadedRectangleEdges<SharedColor>;
using CascadedBorderRadii = CascadedRectangleCorners<Float>;
struct BorderMetrics {
BorderColors borderColors {};
BorderWidths borderWidths {};
BorderRadii borderRadii {};
BorderStyles borderStyles {};
bool operator==(const BorderMetrics &rhs) const {
return
std::tie(this->borderColors, this->borderWidths, this->borderRadii, this->borderStyles) ==
std::tie(rhs.borderColors, rhs.borderWidths, rhs.borderRadii, rhs.borderStyles);
}
bool operator!=(const BorderMetrics &rhs) const {
return !(*this == rhs);
}
};
} // namespace react
} // namespace facebook

View File

@ -13,7 +13,7 @@
namespace facebook {
namespace react {
static std::array<YGValue, 2> convertRawProp(
static inline std::array<YGValue, 2> convertRawProp(
const RawProps &rawProps,
const std::string &widthName,
const std::string &heightName,
@ -26,7 +26,7 @@ static std::array<YGValue, 2> convertRawProp(
return dimentions;
}
static std::array<YGValue, YGEdgeCount> convertRawProp(
static inline std::array<YGValue, YGEdgeCount> convertRawProp(
const RawProps &rawProps,
const std::string &prefix,
const std::string &suffix,
@ -46,7 +46,7 @@ static std::array<YGValue, YGEdgeCount> convertRawProp(
return result;
}
static std::array<YGValue, YGEdgeCount> convertRawProp(
static inline std::array<YGValue, YGEdgeCount> convertRawProp(
const RawProps &rawProps,
const std::array<YGValue, YGEdgeCount> &sourceValue,
const std::array<YGValue, YGEdgeCount> &defaultValue
@ -61,7 +61,7 @@ static std::array<YGValue, YGEdgeCount> convertRawProp(
return result;
}
static YGStyle convertRawProp(const RawProps &rawProps, const YGStyle &sourceValue) {
static inline YGStyle convertRawProp(const RawProps &rawProps, const YGStyle &sourceValue) {
YGStyle yogaStyle;
yogaStyle.direction = convertRawProp(rawProps, "direction", sourceValue.direction, yogaStyle.direction);
yogaStyle.flexDirection = convertRawProp(rawProps, "flexDirection", sourceValue.flexDirection, yogaStyle.flexDirection);
@ -88,5 +88,53 @@ static YGStyle convertRawProp(const RawProps &rawProps, const YGStyle &sourceVal
return yogaStyle;
}
template <typename T>
static inline CascadedRectangleCorners<T> convertRawProp(
const RawProps &rawProps,
const std::string &prefix,
const std::string &suffix,
const CascadedRectangleCorners<T> &sourceValue
) {
CascadedRectangleCorners<T> result;
result.topLeft = convertRawProp(rawProps, prefix + "TopLeft" + suffix, sourceValue.topLeft);
result.topRight = convertRawProp(rawProps, prefix + "TopRight" + suffix, sourceValue.topRight);
result.bottomLeft = convertRawProp(rawProps, prefix + "BottomLeft" + suffix, sourceValue.bottomLeft);
result.bottomRight = convertRawProp(rawProps, prefix + "BottomRight" + suffix, sourceValue.bottomRight);
result.topStart = convertRawProp(rawProps, prefix + "TopStart" + suffix, sourceValue.topStart);
result.topEnd = convertRawProp(rawProps, prefix + "TopEnd" + suffix, sourceValue.topEnd);
result.bottomStart = convertRawProp(rawProps, prefix + "BottomStart" + suffix, sourceValue.bottomStart);
result.bottomEnd = convertRawProp(rawProps, prefix + "BottomEnd" + suffix, sourceValue.bottomEnd);
result.all = convertRawProp(rawProps, prefix + suffix, sourceValue.all);
return result;
}
template <typename T>
static inline CascadedRectangleEdges<T> convertRawProp(
const RawProps &rawProps,
const std::string &prefix,
const std::string &suffix,
const CascadedRectangleEdges<T> &sourceValue
) {
CascadedRectangleEdges<T> result;
result.left = convertRawProp(rawProps, prefix + "Left" + suffix, sourceValue.left);
result.right = convertRawProp(rawProps, prefix + "Right" + suffix, sourceValue.right);
result.top = convertRawProp(rawProps, prefix + "Top" + suffix, sourceValue.top);
result.bottom = convertRawProp(rawProps, prefix + "Bottom" + suffix, sourceValue.bottom);
result.start = convertRawProp(rawProps, prefix + "Start" + suffix, sourceValue.start);
result.end = convertRawProp(rawProps, prefix + "End" + suffix, sourceValue.end);
result.horizontal = convertRawProp(rawProps, prefix + "Horizontal" + suffix, sourceValue.horizontal);
result.vertical = convertRawProp(rawProps, prefix + "Vertical" + suffix, sourceValue.vertical);
result.all = convertRawProp(rawProps, prefix + suffix, sourceValue.all);
return result;
}
} // namespace react
} // namespace facebook