mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Compare commits
4
Commits
main
...
export-D84250600
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43991b7bab | ||
|
|
d4e972843e | ||
|
|
5c94283be3 | ||
|
|
ca5a0cc765 |
+118
-1
@@ -15,9 +15,10 @@ import type {HostInstance} from 'react-native';
|
||||
|
||||
import ensureInstance from '../../../src/private/__tests__/utilities/ensureInstance';
|
||||
import * as Fantom from '@react-native/fantom';
|
||||
import {createRef} from 'react';
|
||||
import {createRef, useState} from 'react';
|
||||
import {Animated, useAnimatedValue} from 'react-native';
|
||||
import ReactNativeElement from 'react-native/src/private/webapis/dom/nodes/ReactNativeElement';
|
||||
import {allowStyleProp} from 'react-native/Libraries/Animated/NativeAnimatedAllowlist';
|
||||
|
||||
test('animated opacity', () => {
|
||||
let _opacity;
|
||||
@@ -73,3 +74,119 @@ test('animated opacity', () => {
|
||||
<rn-view opacity="0" />,
|
||||
);
|
||||
});
|
||||
|
||||
test('animate layout props', () => {
|
||||
const viewRef = createRef<HostInstance>();
|
||||
allowStyleProp('height');
|
||||
|
||||
let _animatedHeight;
|
||||
let _heightAnimation;
|
||||
|
||||
function MyApp() {
|
||||
const animatedHeight = useAnimatedValue(0);
|
||||
_animatedHeight = animatedHeight;
|
||||
return (
|
||||
<Animated.View
|
||||
ref={viewRef}
|
||||
style={[
|
||||
{
|
||||
width: 100,
|
||||
height: animatedHeight,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<MyApp />);
|
||||
});
|
||||
|
||||
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
|
||||
|
||||
Fantom.runTask(() => {
|
||||
_heightAnimation = Animated.timing(_animatedHeight, {
|
||||
toValue: 100,
|
||||
duration: 10,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
});
|
||||
|
||||
Fantom.unstable_produceFramesForDuration(10);
|
||||
|
||||
// TODO: this shouldn't be neccessary since animation should be stopped after duration
|
||||
Fantom.runTask(() => {
|
||||
_heightAnimation?.stop();
|
||||
});
|
||||
|
||||
// TODO: getFabricUpdateProps is not working with the cloneMutliple method
|
||||
// expect(Fantom.unstable_getFabricUpdateProps(viewElement).height).toBe(100);
|
||||
expect(root.getRenderedOutput({props: ['height']}).toJSX()).toEqual(
|
||||
<rn-view height="100.000000" />,
|
||||
);
|
||||
});
|
||||
|
||||
test('animate layout props and rerender', () => {
|
||||
const viewRef = createRef<HostInstance>();
|
||||
allowStyleProp('height');
|
||||
|
||||
let _animatedHeight;
|
||||
let _heightAnimation;
|
||||
let _setWidth;
|
||||
|
||||
function MyApp() {
|
||||
const animatedHeight = useAnimatedValue(0);
|
||||
const [width, setWidth] = useState(100);
|
||||
_animatedHeight = animatedHeight;
|
||||
_setWidth = setWidth;
|
||||
return (
|
||||
<Animated.View
|
||||
ref={viewRef}
|
||||
style={[
|
||||
{
|
||||
width: width,
|
||||
height: animatedHeight,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const root = Fantom.createRoot();
|
||||
|
||||
Fantom.runTask(() => {
|
||||
root.render(<MyApp />);
|
||||
});
|
||||
|
||||
const viewElement = ensureInstance(viewRef.current, ReactNativeElement);
|
||||
|
||||
Fantom.runTask(() => {
|
||||
_heightAnimation = Animated.timing(_animatedHeight, {
|
||||
toValue: 100,
|
||||
duration: 1000,
|
||||
useNativeDriver: true,
|
||||
}).start();
|
||||
});
|
||||
|
||||
Fantom.unstable_produceFramesForDuration(500);
|
||||
expect(root.getRenderedOutput({props: ['height', 'width']}).toJSX()).toEqual(
|
||||
<rn-view height="50.000000" width="100.000000" />,
|
||||
);
|
||||
|
||||
Fantom.runTask(() => {
|
||||
_setWidth(200);
|
||||
});
|
||||
|
||||
// TODO: this shouldn't be neccessary since animation should be stopped after duration
|
||||
Fantom.runTask(() => {
|
||||
_heightAnimation?.stop();
|
||||
});
|
||||
|
||||
// TODO: getFabricUpdateProps is not working with the cloneMutliple method
|
||||
// expect(Fantom.unstable_getFabricUpdateProps(viewElement).height).toBe(50);
|
||||
expect(root.getRenderedOutput({props: ['height', 'width']}).toJSX()).toEqual(
|
||||
<rn-view height="50.000000" width="200.000000" />,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -14,6 +14,7 @@ import type {AnimatedStyleAllowlist} from './AnimatedStyle';
|
||||
|
||||
import NativeAnimatedHelper from '../../../src/private/animated/NativeAnimatedHelper';
|
||||
import {findNodeHandle} from '../../ReactNative/RendererProxy';
|
||||
import {getInternalInstanceHandleFromPublicInstance} from '../../ReactPrivate/ReactNativePrivateInterface';
|
||||
import flattenStyle from '../../StyleSheet/flattenStyle';
|
||||
import {AnimatedEvent} from '../AnimatedEvent';
|
||||
import AnimatedNode from './AnimatedNode';
|
||||
@@ -248,6 +249,7 @@ export default class AnimatedProps extends AnimatedNode {
|
||||
|
||||
if (this._target != null) {
|
||||
this.#connectAnimatedView(this._target);
|
||||
this.#connectShadowNode(this._target);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -259,6 +261,7 @@ export default class AnimatedProps extends AnimatedNode {
|
||||
this._target = {instance, connectedViewTag: null};
|
||||
if (this.__isNative) {
|
||||
this.#connectAnimatedView(this._target);
|
||||
this.#connectShadowNode(this._target);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -279,6 +282,17 @@ export default class AnimatedProps extends AnimatedNode {
|
||||
target.connectedViewTag = viewTag;
|
||||
}
|
||||
|
||||
#connectShadowNode(target: TargetView): void {
|
||||
invariant(this.__isNative, 'Expected node to be marked as "native"');
|
||||
let shadowNode = getInternalInstanceHandleFromPublicInstance(
|
||||
target.instance,
|
||||
).stateNode.node;
|
||||
NativeAnimatedHelper.API.connectAnimatedNodeToShadowNode(
|
||||
this.__getNativeTag(),
|
||||
shadowNode,
|
||||
);
|
||||
}
|
||||
|
||||
#disconnectAnimatedView(target: TargetView): void {
|
||||
invariant(this.__isNative, 'Expected node to be marked as "native"');
|
||||
const viewTag = target.connectedViewTag;
|
||||
|
||||
@@ -167,6 +167,11 @@ RCT_EXPORT_METHOD(connectAnimatedNodeToView : (double)nodeTag viewTag : (double)
|
||||
}];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(connectAnimatedNodeToShadowNode:(double)nodeTag shadowNode:(NSDictionary *)shadowNode)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(disconnectAnimatedNodeFromView : (double)nodeTag viewTag : (double)viewTag)
|
||||
{
|
||||
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
|
||||
|
||||
@@ -158,6 +158,17 @@ void AnimatedModule::connectAnimatedNodeToView(
|
||||
ConnectAnimatedNodeToViewOp{.nodeTag = nodeTag, .viewTag = viewTag});
|
||||
}
|
||||
|
||||
void AnimatedModule::connectAnimatedNodeToShadowNode(
|
||||
jsi::Runtime& rt,
|
||||
Tag nodeTag,
|
||||
jsi::Object shadowNode) {
|
||||
auto nativeState = shadowNode.getNativeState(rt);
|
||||
auto shadowNodeWrapper =
|
||||
std::dynamic_pointer_cast<ShadowNodeWrapper>(nativeState);
|
||||
operations_.emplace_back(ConnectAnimatedNodeToShadowNodeOp{
|
||||
.nodeTag = nodeTag, .shadowNode = shadowNodeWrapper->shadowNode});
|
||||
}
|
||||
|
||||
void AnimatedModule::disconnectAnimatedNodeFromView(
|
||||
jsi::Runtime& /*rt*/,
|
||||
Tag nodeTag,
|
||||
@@ -270,6 +281,11 @@ void AnimatedModule::executeOperation(const Operation& operation) {
|
||||
nodesManager_->extractAnimatedNodeOffsetOp(op.nodeTag);
|
||||
} else if constexpr (std::is_same_v<T, ConnectAnimatedNodeToViewOp>) {
|
||||
nodesManager_->connectAnimatedNodeToView(op.nodeTag, op.viewTag);
|
||||
} else if constexpr (std::is_same_v<
|
||||
T,
|
||||
ConnectAnimatedNodeToShadowNodeOp>) {
|
||||
nodesManager_->connectAnimatedNodeToShadowNode(
|
||||
op.nodeTag, op.shadowNode);
|
||||
} else if constexpr (std::is_same_v<
|
||||
T,
|
||||
DisconnectAnimatedNodeFromViewOp>) {
|
||||
|
||||
@@ -88,6 +88,11 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec<AnimatedModule>,
|
||||
Tag viewTag{};
|
||||
};
|
||||
|
||||
struct ConnectAnimatedNodeToShadowNodeOp {
|
||||
Tag nodeTag{};
|
||||
ShadowNode::Shared shadowNode{};
|
||||
};
|
||||
|
||||
struct DisconnectAnimatedNodeFromViewOp {
|
||||
Tag nodeTag{};
|
||||
Tag viewTag{};
|
||||
@@ -125,6 +130,7 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec<AnimatedModule>,
|
||||
SetAnimatedNodeOffsetOp,
|
||||
SetAnimatedNodeValueOp,
|
||||
ConnectAnimatedNodeToViewOp,
|
||||
ConnectAnimatedNodeToShadowNodeOp,
|
||||
DisconnectAnimatedNodeFromViewOp,
|
||||
RestoreDefaultValuesOp,
|
||||
FlattenAnimatedNodeOffsetOp,
|
||||
@@ -178,6 +184,11 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec<AnimatedModule>,
|
||||
|
||||
void connectAnimatedNodeToView(jsi::Runtime& rt, Tag nodeTag, Tag viewTag);
|
||||
|
||||
void connectAnimatedNodeToShadowNode(
|
||||
jsi::Runtime& rt,
|
||||
Tag nodeTag,
|
||||
jsi::Object shadowNode);
|
||||
|
||||
void
|
||||
disconnectAnimatedNodeFromView(jsi::Runtime& rt, Tag nodeTag, Tag viewTag);
|
||||
|
||||
|
||||
+53
-3
@@ -234,6 +234,20 @@ void NativeAnimatedNodesManager::connectAnimatedNodeToView(
|
||||
}
|
||||
}
|
||||
|
||||
void NativeAnimatedNodesManager::connectAnimatedNodeToShadowNode(
|
||||
Tag propsNodeTag,
|
||||
ShadowNode::Shared shadowNode) noexcept {
|
||||
react_native_assert(propsNodeTag);
|
||||
auto node = getAnimatedNode<PropsAnimatedNode>(propsNodeTag);
|
||||
if (node != nullptr) {
|
||||
node->connectToShadowNode(shadowNode);
|
||||
updatedNodeTags_.insert(node->tag());
|
||||
} else {
|
||||
LOG(WARNING)
|
||||
<< "Cannot ConnectAnimatedNodeToShadowNode, animated node has to be props type";
|
||||
}
|
||||
}
|
||||
|
||||
void NativeAnimatedNodesManager::disconnectAnimatedNodeFromView(
|
||||
Tag propsNodeTag,
|
||||
Tag viewTag) noexcept {
|
||||
@@ -861,10 +875,14 @@ void NativeAnimatedNodesManager::schedulePropsCommit(
|
||||
Tag viewTag,
|
||||
const folly::dynamic& props,
|
||||
bool layoutStyleUpdated,
|
||||
bool forceFabricCommit) noexcept {
|
||||
bool forceFabricCommit,
|
||||
ShadowNode::Shared shadowNode) noexcept {
|
||||
if (ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
|
||||
if (layoutStyleUpdated) {
|
||||
mergeObjects(updateViewProps_[viewTag], props);
|
||||
if (shadowNode) {
|
||||
tagToShadowNode_[viewTag] = std::move(shadowNode);
|
||||
}
|
||||
} else {
|
||||
mergeObjects(updateViewPropsDirect_[viewTag], props);
|
||||
}
|
||||
@@ -975,6 +993,28 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
|
||||
AnimationMutation{tag, nullptr, propsBuilder.get()});
|
||||
containsChange = true;
|
||||
}
|
||||
for (auto& [tag, props] : updateViewProps_) {
|
||||
auto node = tagToShadowNode_.find(tag);
|
||||
if (node != tagToShadowNode_.end()) {
|
||||
if (props.find("width") != props.items().end()) {
|
||||
propsBuilder.setWidth(
|
||||
yoga::Style::SizeLength::points(props["width"].asDouble()));
|
||||
}
|
||||
if (props.find("height") != props.items().end()) {
|
||||
propsBuilder.setHeight(
|
||||
yoga::Style::SizeLength::points(props["height"].asDouble()));
|
||||
}
|
||||
// propsBuilder.storeDynamic(props);
|
||||
mutations.push_back(AnimationMutation{
|
||||
tag, &node->second->getFamily(), propsBuilder.get()});
|
||||
}
|
||||
containsChange = true;
|
||||
}
|
||||
if (containsChange) {
|
||||
updateViewPropsDirect_.clear();
|
||||
updateViewProps_.clear();
|
||||
tagToShadowNode_.clear();
|
||||
}
|
||||
}
|
||||
|
||||
if (!containsChange) {
|
||||
@@ -994,7 +1034,8 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: update all nodes that are connected to the finished animations.
|
||||
// Step 2: update all nodes that are connected to the finished
|
||||
// animations.
|
||||
updateNodes(finishedAnimationValueNodes);
|
||||
|
||||
isEventAnimationInProgress_ = false;
|
||||
@@ -1005,6 +1046,14 @@ AnimationMutations NativeAnimatedNodesManager::pullAnimationMutations() {
|
||||
mutations.push_back(
|
||||
AnimationMutation{tag, nullptr, propsBuilder.get()});
|
||||
}
|
||||
for (auto& [tag, props] : updateViewProps_) {
|
||||
auto node = tagToShadowNode_.find(tag);
|
||||
if (node != tagToShadowNode_.end()) {
|
||||
propsBuilder.storeDynamic(props);
|
||||
mutations.push_back(AnimationMutation{
|
||||
tag, &node->second->getFamily(), propsBuilder.get()});
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// There is no active animation. Stop the render callback.
|
||||
@@ -1073,7 +1122,8 @@ void NativeAnimatedNodesManager::onRender() {
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: update all nodes that are connected to the finished animations.
|
||||
// Step 2: update all nodes that are connected to the finished
|
||||
// animations.
|
||||
updateNodes(finishedAnimationValueNodes);
|
||||
|
||||
isEventAnimationInProgress_ = false;
|
||||
|
||||
+7
-1
@@ -100,6 +100,10 @@ class NativeAnimatedNodesManager {
|
||||
|
||||
void connectAnimatedNodeToView(Tag propsNodeTag, Tag viewTag) noexcept;
|
||||
|
||||
void connectAnimatedNodeToShadowNode(
|
||||
Tag propsNodeTag,
|
||||
ShadowNode::Shared node) noexcept;
|
||||
|
||||
void disconnectAnimatedNodes(Tag parentTag, Tag childTag) noexcept;
|
||||
|
||||
void disconnectAnimatedNodeFromView(Tag propsNodeTag, Tag viewTag) noexcept;
|
||||
@@ -158,7 +162,8 @@ class NativeAnimatedNodesManager {
|
||||
Tag viewTag,
|
||||
const folly::dynamic& props,
|
||||
bool layoutStyleUpdated,
|
||||
bool forceFabricCommit) noexcept;
|
||||
bool forceFabricCommit,
|
||||
ShadowNode::Shared shadowNode = nullptr) noexcept;
|
||||
|
||||
/**
|
||||
* Commits all pending animated property updates to their respective views.
|
||||
@@ -266,6 +271,7 @@ class NativeAnimatedNodesManager {
|
||||
|
||||
std::unordered_map<Tag, folly::dynamic> updateViewProps_{};
|
||||
std::unordered_map<Tag, folly::dynamic> updateViewPropsDirect_{};
|
||||
std::unordered_map<Tag, ShadowNode::Shared> tagToShadowNode_{};
|
||||
|
||||
/*
|
||||
* Sometimes a view is not longer connected to a PropsAnimatedNode, but
|
||||
|
||||
+37
-29
@@ -82,6 +82,12 @@ NativeAnimatedNodesManagerProvider::getOrCreate(
|
||||
nativeAnimatedNodesManager_ =
|
||||
std::make_shared<NativeAnimatedNodesManager>(animationBackend_);
|
||||
|
||||
nativeAnimatedDelegate_ =
|
||||
std::make_shared<UIManagerNativeAnimatedDelegateBackendImpl>(
|
||||
animationBackend_);
|
||||
|
||||
uiManager->setNativeAnimatedDelegate(nativeAnimatedDelegate_);
|
||||
|
||||
uiManager->unstable_setAnimationBackend(animationBackend_);
|
||||
} else {
|
||||
nativeAnimatedNodesManager_ =
|
||||
@@ -90,6 +96,12 @@ NativeAnimatedNodesManagerProvider::getOrCreate(
|
||||
std::move(fabricCommitCallback),
|
||||
std::move(startOnRenderCallback_),
|
||||
std::move(stopOnRenderCallback_));
|
||||
|
||||
nativeAnimatedDelegate_ =
|
||||
std::make_shared<UIManagerNativeAnimatedDelegateImpl>(
|
||||
nativeAnimatedNodesManager_);
|
||||
|
||||
uiManager->setNativeAnimatedDelegate(nativeAnimatedDelegate_);
|
||||
}
|
||||
|
||||
addEventEmitterListener(
|
||||
@@ -112,36 +124,32 @@ NativeAnimatedNodesManagerProvider::getOrCreate(
|
||||
return false;
|
||||
}));
|
||||
|
||||
nativeAnimatedDelegate_ =
|
||||
std::make_shared<UIManagerNativeAnimatedDelegateImpl>(
|
||||
nativeAnimatedNodesManager_);
|
||||
if (!ReactNativeFeatureFlags::useSharedAnimatedBackend()) {
|
||||
// TODO: remove force casting.
|
||||
auto* scheduler = (Scheduler*)uiManager->getDelegate();
|
||||
animatedMountingOverrideDelegate_ =
|
||||
std::make_shared<AnimatedMountingOverrideDelegate>(
|
||||
*nativeAnimatedNodesManager_, *scheduler);
|
||||
|
||||
uiManager->setNativeAnimatedDelegate(nativeAnimatedDelegate_);
|
||||
|
||||
// TODO: remove force casting.
|
||||
auto* scheduler = (Scheduler*)uiManager->getDelegate();
|
||||
animatedMountingOverrideDelegate_ =
|
||||
std::make_shared<AnimatedMountingOverrideDelegate>(
|
||||
*nativeAnimatedNodesManager_, *scheduler);
|
||||
|
||||
// Register on existing surfaces
|
||||
uiManager->getShadowTreeRegistry().enumerate(
|
||||
[animatedMountingOverrideDelegate =
|
||||
std::weak_ptr<const AnimatedMountingOverrideDelegate>(
|
||||
animatedMountingOverrideDelegate_)](
|
||||
const ShadowTree& shadowTree, bool& /*stop*/) {
|
||||
shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
|
||||
animatedMountingOverrideDelegate);
|
||||
});
|
||||
// Register on surfaces started in the future
|
||||
uiManager->setOnSurfaceStartCallback(
|
||||
[animatedMountingOverrideDelegate =
|
||||
std::weak_ptr<const AnimatedMountingOverrideDelegate>(
|
||||
animatedMountingOverrideDelegate_)](
|
||||
const ShadowTree& shadowTree) {
|
||||
shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
|
||||
animatedMountingOverrideDelegate);
|
||||
});
|
||||
// Register on existing surfaces
|
||||
uiManager->getShadowTreeRegistry().enumerate(
|
||||
[animatedMountingOverrideDelegate =
|
||||
std::weak_ptr<const AnimatedMountingOverrideDelegate>(
|
||||
animatedMountingOverrideDelegate_)](
|
||||
const ShadowTree& shadowTree, bool& /*stop*/) {
|
||||
shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
|
||||
animatedMountingOverrideDelegate);
|
||||
});
|
||||
// Register on surfaces started in the future
|
||||
uiManager->setOnSurfaceStartCallback(
|
||||
[animatedMountingOverrideDelegate =
|
||||
std::weak_ptr<const AnimatedMountingOverrideDelegate>(
|
||||
animatedMountingOverrideDelegate_)](
|
||||
const ShadowTree& shadowTree) {
|
||||
shadowTree.getMountingCoordinator()->setMountingOverrideDelegate(
|
||||
animatedMountingOverrideDelegate);
|
||||
});
|
||||
}
|
||||
}
|
||||
return nativeAnimatedNodesManager_;
|
||||
}
|
||||
|
||||
+10
-1
@@ -58,6 +58,11 @@ void PropsAnimatedNode::connectToView(Tag viewTag) {
|
||||
connectedViewTag_ = viewTag;
|
||||
}
|
||||
|
||||
void PropsAnimatedNode::connectToShadowNode(
|
||||
std::shared_ptr<const ShadowNode> shadowNode) {
|
||||
viewShadowNode_ = shadowNode;
|
||||
}
|
||||
|
||||
void PropsAnimatedNode::disconnectFromView(Tag viewTag) {
|
||||
react_native_assert(
|
||||
connectedViewTag_ == viewTag &&
|
||||
@@ -144,7 +149,11 @@ void PropsAnimatedNode::update(bool forceFabricCommit) {
|
||||
layoutStyleUpdated_ = isLayoutStyleUpdated(getConfig()["props"], *manager_);
|
||||
|
||||
manager_->schedulePropsCommit(
|
||||
connectedViewTag_, props_, layoutStyleUpdated_, forceFabricCommit);
|
||||
connectedViewTag_,
|
||||
props_,
|
||||
layoutStyleUpdated_,
|
||||
forceFabricCommit,
|
||||
viewShadowNode_.lock());
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "AnimatedNode.h"
|
||||
|
||||
#include <react/renderer/animated/internal/primitives.h>
|
||||
#include <react/renderer/core/ShadowNode.h>
|
||||
#include <mutex>
|
||||
|
||||
namespace facebook::react {
|
||||
@@ -24,6 +25,7 @@ class PropsAnimatedNode final : public AnimatedNode {
|
||||
const folly::dynamic& config,
|
||||
NativeAnimatedNodesManager& manager);
|
||||
void connectToView(Tag viewTag);
|
||||
void connectToShadowNode(std::shared_ptr<const ShadowNode> shadowNode);
|
||||
void disconnectFromView(Tag viewTag);
|
||||
void restoreDefaultValues();
|
||||
|
||||
@@ -46,5 +48,6 @@ class PropsAnimatedNode final : public AnimatedNode {
|
||||
bool layoutStyleUpdated_{false};
|
||||
|
||||
Tag connectedViewTag_{animated::undefinedAnimatedNodeIdentifier};
|
||||
std::weak_ptr<const ShadowNode> viewShadowNode_{};
|
||||
};
|
||||
} // namespace facebook::react
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include "AnimatedPropsRegistry.h"
|
||||
#include <react/renderer/core/PropsParserContext.h>
|
||||
#include "AnimatedProps.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
void AnimatedPropsRegistry::update(
|
||||
std::unordered_map<Tag, AnimatedProps>& animatedPropsMap,
|
||||
std::unordered_set<const ShadowNodeFamily*>& families) {
|
||||
for (const auto& family : families) {
|
||||
if (family != nullptr) {
|
||||
surfaceToFamilies_[family->getSurfaceId()].insert(family);
|
||||
}
|
||||
}
|
||||
for (auto& [tag, animatedProps] : animatedPropsMap) {
|
||||
auto& snapshot = map_[tag];
|
||||
auto& viewProps = snapshot.props;
|
||||
|
||||
for (auto& animatedProp : animatedProps.props) {
|
||||
snapshot.propNames.insert(animatedProp->propName);
|
||||
switch (animatedProp->propName) {
|
||||
case OPACITY:
|
||||
viewProps.opacity = get<Float>(animatedProp);
|
||||
break;
|
||||
|
||||
case WIDTH:
|
||||
viewProps.yogaStyle.setDimension(
|
||||
yoga::Dimension::Width,
|
||||
get<yoga::Style::SizeLength>(animatedProp));
|
||||
break;
|
||||
|
||||
case HEIGHT:
|
||||
viewProps.yogaStyle.setDimension(
|
||||
yoga::Dimension::Height,
|
||||
get<yoga::Style::SizeLength>(animatedProp));
|
||||
break;
|
||||
|
||||
case BORDER_RADII:
|
||||
viewProps.borderRadii = get<CascadedBorderRadii>(animatedProp);
|
||||
break;
|
||||
|
||||
case FLEX:
|
||||
viewProps.yogaStyle.setFlex(get<yoga::FloatOptional>(animatedProp));
|
||||
break;
|
||||
|
||||
case TRANSFORM:
|
||||
viewProps.transform = get<Transform>(animatedProp);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <folly/dynamic.h>
|
||||
#include <react/renderer/components/view/BaseViewProps.h>
|
||||
#include <react/renderer/core/ReactPrimitives.h>
|
||||
#include <react/renderer/uimanager/UIManager.h>
|
||||
#include <react/renderer/uimanager/UIManagerCommitHook.h>
|
||||
#include "AnimatedProps.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
struct PropsSnapshot {
|
||||
BaseViewProps props;
|
||||
std::unordered_set<PropName> propNames;
|
||||
};
|
||||
|
||||
class AnimatedPropsRegistry {
|
||||
public:
|
||||
std::unordered_map<Tag, PropsSnapshot> map_;
|
||||
std::unordered_map<SurfaceId, std::unordered_set<const ShadowNodeFamily*>>
|
||||
surfaceToFamilies_;
|
||||
|
||||
void update(
|
||||
std::unordered_map<Tag, AnimatedProps>& animatedPropsMap,
|
||||
std::unordered_set<const ShadowNodeFamily*>& families);
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
+64
-26
@@ -7,9 +7,22 @@
|
||||
|
||||
#include "AnimationBackend.h"
|
||||
#include <chrono>
|
||||
#include "AnimatedPropsRegistry.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
UIManagerNativeAnimatedDelegateBackendImpl::
|
||||
UIManagerNativeAnimatedDelegateBackendImpl(
|
||||
std::weak_ptr<AnimationBackend> animationBackend)
|
||||
: animationBackend_(std::move(animationBackend)) {}
|
||||
|
||||
void UIManagerNativeAnimatedDelegateBackendImpl::runAnimationFrame() {
|
||||
if (auto animationBackendStrong = animationBackend_.lock()) {
|
||||
animationBackendStrong->onAnimationFrame(
|
||||
std::chrono::steady_clock::now().time_since_epoch().count() / 1000);
|
||||
}
|
||||
}
|
||||
|
||||
static inline Props::Shared cloneProps(
|
||||
AnimatedProps& animatedProps,
|
||||
const ShadowNode& shadowNode) {
|
||||
@@ -83,7 +96,11 @@ AnimationBackend::AnimationBackend(
|
||||
stopOnRenderCallback_(std::move(stopOnRenderCallback)),
|
||||
directManipulationCallback_(std::move(directManipulationCallback)),
|
||||
fabricCommitCallback_(std::move(fabricCommitCallback)),
|
||||
uiManager_(uiManager) {}
|
||||
animatedPropsRegistry_(std::make_shared<AnimatedPropsRegistry>()),
|
||||
uiManager_(uiManager),
|
||||
commitHook_(std::make_unique<AnimationBackendCommitHook>(
|
||||
uiManager,
|
||||
animatedPropsRegistry_)) {}
|
||||
|
||||
void AnimationBackend::onAnimationFrame(double timestamp) {
|
||||
std::unordered_map<Tag, AnimatedProps> updates;
|
||||
@@ -98,6 +115,8 @@ void AnimationBackend::onAnimationFrame(double timestamp) {
|
||||
}
|
||||
}
|
||||
|
||||
animatedPropsRegistry_->update(updates, families);
|
||||
|
||||
if (hasAnyLayoutUpdates) {
|
||||
commitUpdatesWithFamilies(families, updates);
|
||||
} else {
|
||||
@@ -108,39 +127,58 @@ void AnimationBackend::onAnimationFrame(double timestamp) {
|
||||
void AnimationBackend::start(const Callback& callback) {
|
||||
callbacks.push_back(callback);
|
||||
// TODO: startOnRenderCallback_ should provide the timestamp from the platform
|
||||
startOnRenderCallback_([this]() {
|
||||
onAnimationFrame(
|
||||
std::chrono::steady_clock::now().time_since_epoch().count() / 1000);
|
||||
});
|
||||
if (startOnRenderCallback_) {
|
||||
startOnRenderCallback_([this]() {
|
||||
onAnimationFrame(
|
||||
std::chrono::steady_clock::now().time_since_epoch().count() / 1000);
|
||||
});
|
||||
}
|
||||
}
|
||||
void AnimationBackend::stop() {
|
||||
stopOnRenderCallback_();
|
||||
if (stopOnRenderCallback_) {
|
||||
stopOnRenderCallback_();
|
||||
}
|
||||
callbacks.clear();
|
||||
}
|
||||
|
||||
void AnimationBackend::commitUpdatesWithFamilies(
|
||||
const std::unordered_set<const ShadowNodeFamily*>& families,
|
||||
std::unordered_map<Tag, AnimatedProps>& updates) {
|
||||
uiManager_->getShadowTreeRegistry().enumerate(
|
||||
[families, &updates](const ShadowTree& shadowTree, bool& /*stop*/) {
|
||||
shadowTree.commit(
|
||||
[families, &updates](const RootShadowNode& oldRootShadowNode) {
|
||||
return std::static_pointer_cast<RootShadowNode>(
|
||||
oldRootShadowNode.cloneMultiple(
|
||||
families,
|
||||
[families, &updates](
|
||||
const ShadowNode& shadowNode,
|
||||
const ShadowNodeFragment& fragment) {
|
||||
auto& animatedProps = updates.at(shadowNode.getTag());
|
||||
auto newProps = cloneProps(animatedProps, shadowNode);
|
||||
return shadowNode.clone(
|
||||
{newProps,
|
||||
fragment.children,
|
||||
shadowNode.getState()});
|
||||
}));
|
||||
},
|
||||
{.mountSynchronously = true});
|
||||
});
|
||||
std::unordered_map<SurfaceId, std::unordered_set<const ShadowNodeFamily*>>
|
||||
surfaceToFamilies;
|
||||
for (auto& family : families) {
|
||||
surfaceToFamilies[family->getSurfaceId()].insert(family);
|
||||
}
|
||||
for (const auto& [surfaceId, surfaceFamilies] : surfaceToFamilies) {
|
||||
uiManager_->getShadowTreeRegistry().visit(
|
||||
surfaceId, [&surfaceFamilies, &updates](const ShadowTree& shadowTree) {
|
||||
shadowTree.commit(
|
||||
[&surfaceFamilies,
|
||||
&updates](const RootShadowNode& oldRootShadowNode) {
|
||||
return std::static_pointer_cast<RootShadowNode>(
|
||||
oldRootShadowNode.cloneMultiple(
|
||||
surfaceFamilies,
|
||||
[&surfaceFamilies, &updates](
|
||||
const ShadowNode& shadowNode,
|
||||
const ShadowNodeFragment& fragment) {
|
||||
auto newProps =
|
||||
ShadowNodeFragment::propsPlaceholder();
|
||||
if (surfaceFamilies.contains(
|
||||
&shadowNode.getFamily())) {
|
||||
auto& animatedProps =
|
||||
updates.at(shadowNode.getTag());
|
||||
newProps = cloneProps(animatedProps, shadowNode);
|
||||
}
|
||||
return shadowNode.clone(
|
||||
{.props = newProps,
|
||||
.children = fragment.children,
|
||||
.state = shadowNode.getState(),
|
||||
.runtimeShadowNodeReference = false});
|
||||
}));
|
||||
},
|
||||
{.mountSynchronously = true});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationBackend::synchronouslyUpdateProps(
|
||||
|
||||
@@ -15,9 +15,25 @@
|
||||
#include <vector>
|
||||
#include "AnimatedProps.h"
|
||||
#include "AnimatedPropsBuilder.h"
|
||||
#include "AnimatedPropsRegistry.h"
|
||||
#include "AnimationBackendCommitHook.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class AnimationBackend;
|
||||
|
||||
class UIManagerNativeAnimatedDelegateBackendImpl
|
||||
: public UIManagerNativeAnimatedDelegate {
|
||||
public:
|
||||
explicit UIManagerNativeAnimatedDelegateBackendImpl(
|
||||
std::weak_ptr<AnimationBackend> animationBackend);
|
||||
|
||||
void runAnimationFrame() override;
|
||||
|
||||
private:
|
||||
std::weak_ptr<AnimationBackend> animationBackend_;
|
||||
};
|
||||
|
||||
struct AnimationMutation {
|
||||
Tag tag;
|
||||
const ShadowNodeFamily* family;
|
||||
@@ -41,7 +57,9 @@ class AnimationBackend : public UIManagerAnimationBackend {
|
||||
const StopOnRenderCallback stopOnRenderCallback_;
|
||||
const DirectManipulationCallback directManipulationCallback_;
|
||||
const FabricCommitCallback fabricCommitCallback_;
|
||||
std::shared_ptr<AnimatedPropsRegistry> animatedPropsRegistry_;
|
||||
UIManager* uiManager_;
|
||||
std::unique_ptr<AnimationBackendCommitHook> commitHook_;
|
||||
|
||||
AnimationBackend(
|
||||
StartOnRenderCallback&& startOnRenderCallback,
|
||||
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#include <react/renderer/animationbackend/AnimationBackendCommitHook.h>
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
AnimationBackendCommitHook::AnimationBackendCommitHook(
|
||||
UIManager* uiManager,
|
||||
std::shared_ptr<AnimatedPropsRegistry> animatedPropsRegistry)
|
||||
: uiManager_(uiManager),
|
||||
animatedPropsRegistry_(std::move(animatedPropsRegistry)) {
|
||||
uiManager_->registerCommitHook(*this);
|
||||
}
|
||||
|
||||
RootShadowNode::Unshared AnimationBackendCommitHook::shadowTreeWillCommit(
|
||||
const ShadowTree& shadowTree,
|
||||
const RootShadowNode::Shared& oldRootShadowNode,
|
||||
const RootShadowNode::Unshared& newRootShadowNode,
|
||||
const ShadowTreeCommitOptions& commitOptions) noexcept {
|
||||
if (commitOptions.source != ShadowTreeCommitSource::React) {
|
||||
return newRootShadowNode;
|
||||
}
|
||||
auto surfaceFamilies =
|
||||
animatedPropsRegistry_->surfaceToFamilies_[shadowTree.getSurfaceId()];
|
||||
auto& updates = animatedPropsRegistry_->map_;
|
||||
if (surfaceFamilies.empty()) {
|
||||
return newRootShadowNode;
|
||||
}
|
||||
return std::static_pointer_cast<
|
||||
RootShadowNode>(newRootShadowNode->cloneMultiple(
|
||||
surfaceFamilies,
|
||||
[&surfaceFamilies, &updates](
|
||||
const ShadowNode& shadowNode, const ShadowNodeFragment& fragment) {
|
||||
auto newProps = ShadowNodeFragment::propsPlaceholder();
|
||||
std::shared_ptr<BaseViewProps> viewProps = nullptr;
|
||||
if (surfaceFamilies.contains(&shadowNode.getFamily())) {
|
||||
auto& snapshot = updates.at(shadowNode.getTag());
|
||||
if (!snapshot.propNames.empty()) {
|
||||
PropsParserContext propsParserContext{
|
||||
shadowNode.getSurfaceId(), *shadowNode.getContextContainer()};
|
||||
|
||||
newProps = shadowNode.getComponentDescriptor().cloneProps(
|
||||
propsParserContext, shadowNode.getProps(), {});
|
||||
viewProps = std::const_pointer_cast<BaseViewProps>(
|
||||
std::static_pointer_cast<const BaseViewProps>(newProps));
|
||||
}
|
||||
|
||||
for (const auto& propName : snapshot.propNames) {
|
||||
switch (propName) {
|
||||
case OPACITY:
|
||||
viewProps->opacity = snapshot.props.opacity;
|
||||
break;
|
||||
|
||||
case WIDTH:
|
||||
viewProps->yogaStyle.setDimension(
|
||||
yoga::Dimension::Width,
|
||||
snapshot.props.yogaStyle.dimension(yoga::Dimension::Width));
|
||||
break;
|
||||
|
||||
case HEIGHT:
|
||||
viewProps->yogaStyle.setDimension(
|
||||
yoga::Dimension::Height,
|
||||
snapshot.props.yogaStyle.dimension(
|
||||
yoga::Dimension::Height));
|
||||
break;
|
||||
|
||||
case TRANSFORM:
|
||||
viewProps->transform = snapshot.props.transform;
|
||||
break;
|
||||
|
||||
case BORDER_RADII:
|
||||
viewProps->borderRadii = snapshot.props.borderRadii;
|
||||
break;
|
||||
|
||||
case FLEX:
|
||||
viewProps->yogaStyle.setFlex(snapshot.props.yogaStyle.flex());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return shadowNode.clone(
|
||||
{.props = newProps,
|
||||
.children = fragment.children,
|
||||
.state = shadowNode.getState(),
|
||||
.runtimeShadowNodeReference = false});
|
||||
}));
|
||||
}
|
||||
|
||||
AnimationBackendCommitHook::~AnimationBackendCommitHook() {
|
||||
// TODO: fix
|
||||
// uiManager get's deallocated first, so we can't call unregisterCommitHook
|
||||
// here (or need to get a shared_ptr) uiManager_->unregisterCommitHook(*this);
|
||||
}
|
||||
|
||||
} // namespace facebook::react
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <folly/dynamic.h>
|
||||
#include <react/renderer/core/ReactPrimitives.h>
|
||||
#include <react/renderer/uimanager/UIManager.h>
|
||||
#include <react/renderer/uimanager/UIManagerCommitHook.h>
|
||||
#include "AnimatedPropsRegistry.h"
|
||||
|
||||
namespace facebook::react {
|
||||
|
||||
class AnimationBackendCommitHook : public UIManagerCommitHook {
|
||||
UIManager* uiManager_;
|
||||
std::shared_ptr<AnimatedPropsRegistry> animatedPropsRegistry_;
|
||||
|
||||
public:
|
||||
AnimationBackendCommitHook(
|
||||
UIManager* uiManager,
|
||||
std::shared_ptr<AnimatedPropsRegistry> animatedPropsRegistry);
|
||||
RootShadowNode::Unshared shadowTreeWillCommit(
|
||||
const ShadowTree& shadowTree,
|
||||
const RootShadowNode::Shared& oldRootShadowNode,
|
||||
const RootShadowNode::Unshared& newRootShadowNode,
|
||||
const ShadowTreeCommitOptions& commitOptions) noexcept override;
|
||||
void commitHookWasRegistered(const UIManager& uiManager) noexcept override {}
|
||||
void commitHookWasUnregistered(const UIManager& uiManager) noexcept override {
|
||||
}
|
||||
~AnimationBackendCommitHook() override;
|
||||
};
|
||||
|
||||
} // namespace facebook::react
|
||||
@@ -435,11 +435,7 @@ std::shared_ptr<ShadowNode> cloneMultipleRecursive(
|
||||
}
|
||||
}
|
||||
|
||||
ShadowNodeFragment fragment{.children = newChildren};
|
||||
if (familiesToUpdate.contains(family)) {
|
||||
return callback(shadowNode, fragment);
|
||||
}
|
||||
return shadowNode.clone(fragment);
|
||||
return callback(shadowNode, {.children = newChildren});
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -83,6 +83,7 @@ function createNativeOperations(): $NonMaybeType<typeof NativeAnimatedModule> {
|
||||
'removeAnimatedEventFromView', // 19
|
||||
'addListener', // 20
|
||||
'removeListener', // 21
|
||||
'connectAnimatedNodeToShadowNode', // 22
|
||||
];
|
||||
const nativeOperations: {
|
||||
[$Values<typeof methodNames>]: (...$ReadOnlyArray<mixed>) => void,
|
||||
@@ -312,6 +313,10 @@ const API = {
|
||||
NativeOperations.connectAnimatedNodeToView(nodeTag, viewTag);
|
||||
},
|
||||
|
||||
connectAnimatedNodeToShadowNode(nodeTag: number, shadowNode: {}): void {
|
||||
NativeOperations.connectAnimatedNodeToShadowNode(nodeTag, shadowNode);
|
||||
},
|
||||
|
||||
disconnectAnimatedNodeFromView(nodeTag: number, viewTag: number): void {
|
||||
NativeOperations.disconnectAnimatedNodeFromView(nodeTag, viewTag);
|
||||
},
|
||||
|
||||
+4
@@ -49,6 +49,10 @@ export interface Spec extends TurboModule {
|
||||
+flattenAnimatedNodeOffset: (nodeTag: number) => void;
|
||||
+extractAnimatedNodeOffset: (nodeTag: number) => void;
|
||||
+connectAnimatedNodeToView: (nodeTag: number, viewTag: number) => void;
|
||||
+connectAnimatedNodeToShadowNode: (
|
||||
nodeTag: number,
|
||||
shadowNode: Object,
|
||||
) => void;
|
||||
+disconnectAnimatedNodeFromView: (nodeTag: number, viewTag: number) => void;
|
||||
+restoreDefaultValues: (nodeTag: number) => void;
|
||||
+dropAnimatedNode: (tag: number) => void;
|
||||
|
||||
+4
@@ -49,6 +49,10 @@ export interface Spec extends TurboModule {
|
||||
+flattenAnimatedNodeOffset: (nodeTag: number) => void;
|
||||
+extractAnimatedNodeOffset: (nodeTag: number) => void;
|
||||
+connectAnimatedNodeToView: (nodeTag: number, viewTag: number) => void;
|
||||
+connectAnimatedNodeToShadowNode: (
|
||||
nodeTag: number,
|
||||
shadowNode: Object,
|
||||
) => void;
|
||||
+disconnectAnimatedNodeFromView: (nodeTag: number, viewTag: number) => void;
|
||||
+restoreDefaultValues: (nodeTag: number) => void;
|
||||
+dropAnimatedNode: (tag: number) => void;
|
||||
|
||||
Reference in New Issue
Block a user