|
2 | 2 |
|
3 | 3 | #import <reanimated/Tools/FeatureFlags.h> |
4 | 4 |
|
| 5 | +#import <react/renderer/components/view/ViewProps.h> |
| 6 | +#import <react/renderer/core/LayoutableShadowNode.h> |
| 7 | +#import <react/renderer/graphics/Color.h> |
| 8 | + |
| 9 | +#import <array> |
| 10 | +#import <memory> |
| 11 | +#import <string_view> |
5 | 12 | #import <variant> |
6 | 13 |
|
| 14 | +using namespace facebook::react; |
7 | 15 | using namespace reanimated::css; |
8 | 16 |
|
| 17 | +// Properties expressible as a CABasicAnimation on the view's main layer. |
| 18 | +static const std::unordered_set<std::string> &routableProperties() |
| 19 | +{ |
| 20 | + static const std::unordered_set<std::string> kRoutable = { |
| 21 | + "opacity", |
| 22 | + "shadowOpacity", |
| 23 | + "shadowRadius", |
| 24 | + "shadowOffset", |
| 25 | + "shadowColor", |
| 26 | + "backgroundColor", |
| 27 | + "borderColor", |
| 28 | + }; |
| 29 | + return kRoutable; |
| 30 | +} |
| 31 | + |
| 32 | +// Per-side colors that subvert the uniform `borderColor` when they transition |
| 33 | +// together with it - RN then draws the border through its custom path, where |
| 34 | +// main-layer animations are invisible. |
| 35 | +static bool hasPerSideBorderColorSibling(const std::unordered_set<std::string> &transitioningProperties) |
| 36 | +{ |
| 37 | + static constexpr std::array<std::string_view, 9> kSiblings = { |
| 38 | + "borderTopColor", |
| 39 | + "borderRightColor", |
| 40 | + "borderBottomColor", |
| 41 | + "borderLeftColor", |
| 42 | + "borderStartColor", |
| 43 | + "borderEndColor", |
| 44 | + "borderBlockColor", |
| 45 | + "borderBlockStartColor", |
| 46 | + "borderBlockEndColor", |
| 47 | + }; |
| 48 | + for (const auto &sibling : kSiblings) { |
| 49 | + if (transitioningProperties.contains(std::string(sibling))) { |
| 50 | + return true; |
| 51 | + } |
| 52 | + } |
| 53 | + return false; |
| 54 | +} |
| 55 | + |
| 56 | +// Mirrors the `useCoreAnimationBorderRendering` condition in RN's |
| 57 | +// RCTViewComponentView. When it fails, RN zeroes the layer's border properties |
| 58 | +// and draws the background + border into dedicated sublayers that get rebuilt |
| 59 | +// (and stripped of animations) on every commit - so main-layer keypath |
| 60 | +// animations of background/border properties would be invisible there. |
| 61 | +static bool usesCoreAnimationBorderRendering(const ShadowNode &shadowNode) |
| 62 | +{ |
| 63 | + const auto *layoutableShadowNode = dynamic_cast<const LayoutableShadowNode *>(&shadowNode); |
| 64 | + if (layoutableShadowNode == nullptr) { |
| 65 | + return false; |
| 66 | + } |
| 67 | + const auto viewProps = std::static_pointer_cast<const ViewProps>(shadowNode.getProps()); |
| 68 | + const auto borderMetrics = viewProps->resolveBorderMetrics(layoutableShadowNode->layoutMetrics_); |
| 69 | + return borderMetrics.borderColors.isUniform() && borderMetrics.borderWidths.isUniform() && |
| 70 | + borderMetrics.borderStyles.isUniform() && borderMetrics.borderStyles.left == BorderStyle::Solid && |
| 71 | + borderMetrics.borderRadii.isUniform() && |
| 72 | + (borderMetrics.borderWidths.left == 0 || viewProps->getClipsContentToBounds() || |
| 73 | + (colorComponentsFromColor(borderMetrics.borderColors.left).alpha == 0 && |
| 74 | + (*borderMetrics.borderColors.left).getUIColor() != nullptr)); |
| 75 | +} |
| 76 | + |
9 | 77 | bool canRouteCSSProperty( |
10 | 78 | const std::string &propertyName, |
11 | 79 | const EasingConfig &easing, |
12 | | - const facebook::react::ShadowNode & /*shadowNode*/, |
13 | | - const std::unordered_set<std::string> & /*transitioningProperties*/) |
| 80 | + const facebook::react::ShadowNode &shadowNode, |
| 81 | + const std::unordered_set<std::string> &transitioningProperties) |
14 | 82 | { |
15 | 83 | if constexpr (!reanimated::StaticFeatureFlags::getFlag("IOS_CSS_CORE_ANIMATION")) { |
16 | 84 | return false; |
17 | 85 | } |
18 | | - if (propertyName != "opacity") { |
| 86 | + if (!routableProperties().contains(propertyName)) { |
19 | 87 | return false; |
20 | 88 | } |
21 | 89 | // CAMediaTimingFunction can express only linear and cubic-bezier curves; |
22 | 90 | // steps / linear-stops easings have to interpolate per-frame on the loop. |
23 | | - return std::holds_alternative<LinearEasing>(easing) || std::holds_alternative<CubicBezierEasing>(easing); |
| 91 | + if (!std::holds_alternative<LinearEasing>(easing) && !std::holds_alternative<CubicBezierEasing>(easing)) { |
| 92 | + return false; |
| 93 | + } |
| 94 | + // Background and border colors live on the main layer only while RN uses |
| 95 | + // Core Animation border rendering for this view. |
| 96 | + if ((propertyName == "backgroundColor" || propertyName == "borderColor") && |
| 97 | + !usesCoreAnimationBorderRendering(shadowNode)) { |
| 98 | + return false; |
| 99 | + } |
| 100 | + if (propertyName == "borderColor" && hasPerSideBorderColorSibling(transitioningProperties)) { |
| 101 | + return false; |
| 102 | + } |
| 103 | + return true; |
24 | 104 | } |
0 commit comments