Skip to content

Commit 5f80493

Browse files
committed
Route shadow and color transitions to Core Animation
1 parent 3878367 commit 5f80493

1 file changed

Lines changed: 73 additions & 3 deletions

File tree

packages/react-native-reanimated/apple/reanimated/apple/CSS/REACSSPlatformProps.mm

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,22 @@
33
#import <reanimated/CSS/utils/props.h>
44
#import <reanimated/Tools/FeatureFlags.h>
55

6+
#import <react/renderer/components/view/ViewProps.h>
7+
#import <react/renderer/core/LayoutableShadowNode.h>
8+
#import <react/renderer/graphics/Color.h>
9+
610
#import <array>
711
#import <cstdint>
12+
#import <memory>
813
#import <string>
14+
#import <string_view>
915
#import <unordered_map>
1016
#import <variant>
1117

1218
namespace reanimated::css {
1319

20+
using namespace facebook::react;
21+
1422
namespace {
1523

1624
// Defined here with the rest of the parse machinery; first consumed once color
@@ -33,6 +41,12 @@
3341
{
3442
static const std::unordered_map<std::string, CSSPropertyTraits> kProperties = {
3543
{"opacity", {CSSValueKind::Scalar, 1.0}},
44+
{"shadowOpacity", {CSSValueKind::Scalar, 1.0}},
45+
{"shadowRadius", {CSSValueKind::Scalar, 0.0}},
46+
{"shadowOffset", {CSSValueKind::Size, std::array<double, 2>{0, 0}}},
47+
{"shadowColor", {CSSValueKind::Color, kBlackColor}},
48+
{"backgroundColor", {CSSValueKind::Color, kTransparentColor}},
49+
{"borderColor", {CSSValueKind::Color, kBlackColor}},
3650
};
3751
const auto it = kProperties.find(propertyName);
3852
return it != kProperties.end() ? &it->second : nullptr;
@@ -60,13 +74,57 @@ CGColorSpaceRef sharedSRGBColorSpace()
6074
return space;
6175
}
6276

77+
// Per-side colors subvert the uniform borderColor when they transition with
78+
// it - RN then draws the border through its custom path, where main-layer
79+
// animations are invisible.
80+
bool hasPerSideBorderColorSibling(const std::unordered_set<std::string> &transitioningProperties)
81+
{
82+
static constexpr std::array<std::string_view, 9> kSiblings = {
83+
"borderTopColor",
84+
"borderRightColor",
85+
"borderBottomColor",
86+
"borderLeftColor",
87+
"borderStartColor",
88+
"borderEndColor",
89+
"borderBlockColor",
90+
"borderBlockStartColor",
91+
"borderBlockEndColor",
92+
};
93+
for (const auto &sibling : kSiblings) {
94+
if (transitioningProperties.contains(std::string(sibling))) {
95+
return true;
96+
}
97+
}
98+
return false;
99+
}
100+
101+
// Mirrors useCoreAnimationBorderRendering in RN's RCTViewComponentView: when
102+
// it is false, RN draws the background and border into dedicated sublayers
103+
// that are rebuilt (and stripped of animations) on every commit, so main-layer
104+
// animations of those properties would be invisible.
105+
bool usesCoreAnimationBorderRendering(const ShadowNode &shadowNode)
106+
{
107+
const auto *layoutableShadowNode = dynamic_cast<const LayoutableShadowNode *>(&shadowNode);
108+
if (layoutableShadowNode == nullptr) {
109+
return false;
110+
}
111+
const auto viewProps = std::static_pointer_cast<const ViewProps>(shadowNode.getProps());
112+
const auto borderMetrics = viewProps->resolveBorderMetrics(layoutableShadowNode->getLayoutMetrics());
113+
return borderMetrics.borderColors.isUniform() && borderMetrics.borderWidths.isUniform() &&
114+
borderMetrics.borderStyles.isUniform() && borderMetrics.borderStyles.left == BorderStyle::Solid &&
115+
borderMetrics.borderRadii.isUniform() &&
116+
(borderMetrics.borderWidths.left == 0 || viewProps->getClipsContentToBounds() ||
117+
(colorComponentsFromColor(borderMetrics.borderColors.left).alpha == 0 &&
118+
(*borderMetrics.borderColors.left).getUIColor() != nullptr));
119+
}
120+
63121
} // namespace
64122

65123
bool canRouteCSSProperty(
66124
const std::string &propertyName,
67125
const EasingConfig &easing,
68-
const facebook::react::ShadowNode & /*shadowNode*/,
69-
const std::unordered_set<std::string> & /*transitioningProperties*/)
126+
const ShadowNode &shadowNode,
127+
const std::unordered_set<std::string> &transitioningProperties)
70128
{
71129
if constexpr (!StaticFeatureFlags::getFlag("IOS_CSS_CORE_ANIMATION")) {
72130
return false;
@@ -76,7 +134,19 @@ bool canRouteCSSProperty(
76134
}
77135
// CAMediaTimingFunction can express only linear and cubic-bezier curves;
78136
// steps / linear-stops easings have to interpolate per-frame on the loop.
79-
return std::holds_alternative<LinearEasing>(easing) || std::holds_alternative<CubicBezierEasing>(easing);
137+
if (!std::holds_alternative<LinearEasing>(easing) && !std::holds_alternative<CubicBezierEasing>(easing)) {
138+
return false;
139+
}
140+
// Background and border colors live on the main layer only while RN uses
141+
// Core Animation border rendering for this view.
142+
if ((propertyName == "backgroundColor" || propertyName == "borderColor") &&
143+
!usesCoreAnimationBorderRendering(shadowNode)) {
144+
return false;
145+
}
146+
if (propertyName == "borderColor" && hasPerSideBorderColorSibling(transitioningProperties)) {
147+
return false;
148+
}
149+
return true;
80150
}
81151

82152
std::optional<PlatformValue> parsePlatformValue(const std::string &propertyName, const CSSPropertyValue &value)

0 commit comments

Comments
 (0)