Skip to content

Commit eab37fc

Browse files
committed
Route shadow and color transitions to Core Animation
1 parent e217415 commit eab37fc

1 file changed

Lines changed: 73 additions & 3 deletions

File tree

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

Lines changed: 73 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,21 @@
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>
11+
#import <memory>
712
#import <string>
13+
#import <string_view>
814
#import <unordered_map>
915
#import <variant>
1016

1117
namespace reanimated::css {
1218

19+
using namespace facebook::react;
20+
1321
namespace {
1422

1523
constexpr std::array<double, 4> kTransparentColor = {0, 0, 0, 0};
@@ -30,6 +38,12 @@
3038
{
3139
static const std::unordered_map<std::string, CSSPropertyTraits> kProperties = {
3240
{"opacity", {CSSValueKind::Scalar, 1.0}},
41+
{"shadowOpacity", {CSSValueKind::Scalar, 1.0}},
42+
{"shadowRadius", {CSSValueKind::Scalar, 0.0}},
43+
{"shadowOffset", {CSSValueKind::Size, std::array<double, 2>{0, 0}}},
44+
{"shadowColor", {CSSValueKind::Color, kBlackColor}},
45+
{"backgroundColor", {CSSValueKind::Color, kTransparentColor}},
46+
{"borderColor", {CSSValueKind::Color, kBlackColor}},
3347
};
3448
const auto it = kProperties.find(propertyName);
3549
return it != kProperties.end() ? &it->second : nullptr;
@@ -57,13 +71,57 @@ CGColorSpaceRef sharedSRGBColorSpace()
5771
return space;
5872
}
5973

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

62120
bool canRouteCSSProperty(
63121
const std::string &propertyName,
64122
const EasingConfig &easing,
65-
const facebook::react::ShadowNode & /*shadowNode*/,
66-
const std::unordered_set<std::string> & /*transitioningProperties*/)
123+
const ShadowNode &shadowNode,
124+
const std::unordered_set<std::string> &transitioningProperties)
67125
{
68126
if constexpr (!StaticFeatureFlags::getFlag("IOS_CSS_CORE_ANIMATION")) {
69127
return false;
@@ -73,7 +131,19 @@ bool canRouteCSSProperty(
73131
}
74132
// CAMediaTimingFunction can express only linear and cubic-bezier curves;
75133
// steps / linear-stops easings have to interpolate per-frame on the loop.
76-
return std::holds_alternative<LinearEasing>(easing) || std::holds_alternative<CubicBezierEasing>(easing);
134+
if (!std::holds_alternative<LinearEasing>(easing) && !std::holds_alternative<CubicBezierEasing>(easing)) {
135+
return false;
136+
}
137+
// Background and border colors live on the main layer only while RN uses
138+
// Core Animation border rendering for this view.
139+
if ((propertyName == "backgroundColor" || propertyName == "borderColor") &&
140+
!usesCoreAnimationBorderRendering(shadowNode)) {
141+
return false;
142+
}
143+
if (propertyName == "borderColor" && hasPerSideBorderColorSibling(transitioningProperties)) {
144+
return false;
145+
}
146+
return true;
77147
}
78148

79149
std::optional<PlatformValue>

0 commit comments

Comments
 (0)