From ddafd94124bed2d4423977fe6e90eb91706f97e0 Mon Sep 17 00:00:00 2001 From: Samuel Susla Date: Mon, 16 Jun 2025 04:50:47 -0700 Subject: [PATCH] implement setOffset (#52034) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/52034 changelog: [internal] implement `setAnimatedNodeOffset`. Reviewed By: christophpurrer Differential Revision: D76604263 fbshipit-source-id: 36b86aada836098f502f8d24af07e2a98adf0446 --- .../Animated/__tests__/Animated-itest.js | 66 +++++++++++++++++++ .../renderer/animated/AnimatedModule.cpp | 9 ++- .../react/renderer/animated/AnimatedModule.h | 6 ++ .../animated/NativeAnimatedNodesManager.cpp | 8 +++ .../animated/NativeAnimatedNodesManager.h | 2 + 5 files changed, 88 insertions(+), 3 deletions(-) diff --git a/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js b/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js index a53ad8b8100..1a01d2fe500 100644 --- a/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js +++ b/packages/react-native/Libraries/Animated/__tests__/Animated-itest.js @@ -197,3 +197,69 @@ test('animated opacity', () => { , ); }); + +test('moving box by 50 points with offset 10', () => { + let _translateX; + const viewRef = createRef(); + + function MyApp() { + const translateX = useAnimatedValue(0); + _translateX = translateX; + return ( + + ); + } + + const root = Fantom.createRoot(); + + Fantom.runTask(() => { + root.render(); + }); + + const viewElement = ensureInstance(viewRef.current, ReactNativeElement); + + expect(viewElement.getBoundingClientRect().x).toBe(0); + + Fantom.runTask(() => { + Animated.timing(_translateX, { + toValue: 50, + duration: 1000, // 1 second + useNativeDriver: true, + }).start(); + }); + + Fantom.runTask(() => { + _translateX.setOffset(10); + }); + + Fantom.unstable_produceFramesForDuration(500); + + // shadow tree is not synchronised yet, position X is still 0. + expect(viewElement.getBoundingClientRect().x).toBe(0); + + expect( + // $FlowFixMe[incompatible-use] + Fantom.unstable_getDirectManipulationProps(viewElement).transform[0] + .translateX, + ).toBeCloseTo(35, 0.001); + + Fantom.unstable_produceFramesForDuration(500); + + expect( + // $FlowFixMe[incompatible-use] + Fantom.unstable_getDirectManipulationProps(viewElement).transform[0] + .translateX, + ).toBeCloseTo(60, 0.001); + + // TODO: this shouldn't be neccessary but C++ Animated still schedules a React state update. + Fantom.runWorkLoop(); +}); diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/animated/AnimatedModule.cpp b/packages/react-native/ReactCxxPlatform/react/renderer/animated/AnimatedModule.cpp index b2535bfd2a2..ec5dda3f049 100644 --- a/packages/react-native/ReactCxxPlatform/react/renderer/animated/AnimatedModule.cpp +++ b/packages/react-native/ReactCxxPlatform/react/renderer/animated/AnimatedModule.cpp @@ -123,9 +123,10 @@ void AnimatedModule::setAnimatedNodeValue( void AnimatedModule::setAnimatedNodeOffset( jsi::Runtime& /*rt*/, - Tag /*nodeTag*/, - double /*offset*/) { - // TODO(T196512946): missing implementation + Tag nodeTag, + double offset) { + operations_.emplace_back( + SetAnimatedNodeOffsetOp{.nodeTag = nodeTag, .offset = offset}); } void AnimatedModule::flattenAnimatedNodeOffset( @@ -245,6 +246,8 @@ void AnimatedModule::executeOperation(const Operation& operation) { nodesManager_->stopAnimation(op.animationId, false); } else if constexpr (std::is_same_v) { nodesManager_->setAnimatedNodeValue(op.nodeTag, op.value); + } else if constexpr (std::is_same_v) { + nodesManager_->setAnimatedNodeOffset(op.nodeTag, op.offset); } else if constexpr (std::is_same_v) { nodesManager_->connectAnimatedNodeToView(op.nodeTag, op.viewTag); } else if constexpr (std::is_same_v< diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/animated/AnimatedModule.h b/packages/react-native/ReactCxxPlatform/react/renderer/animated/AnimatedModule.h index a164c59d2ee..b736365c87c 100644 --- a/packages/react-native/ReactCxxPlatform/react/renderer/animated/AnimatedModule.h +++ b/packages/react-native/ReactCxxPlatform/react/renderer/animated/AnimatedModule.h @@ -66,6 +66,11 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec, double value{}; }; + struct SetAnimatedNodeOffsetOp { + Tag nodeTag{}; + double offset{}; + }; + struct ConnectAnimatedNodeToViewOp { Tag nodeTag{}; Tag viewTag{}; @@ -105,6 +110,7 @@ class AnimatedModule : public NativeAnimatedModuleCxxSpec, DisconnectAnimatedNodesOp, StartAnimatingNodeOp, StopAnimationOp, + SetAnimatedNodeOffsetOp, SetAnimatedNodeValueOp, ConnectAnimatedNodeToViewOp, DisconnectAnimatedNodeFromViewOp, diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.cpp b/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.cpp index 4c5eb9b66ea..e4f2f40d315 100644 --- a/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.cpp +++ b/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.cpp @@ -246,6 +246,14 @@ void NativeAnimatedNodesManager::setAnimatedNodeValue(Tag tag, double value) { } } +void NativeAnimatedNodesManager::setAnimatedNodeOffset(Tag tag, double offset) { + if (auto node = getAnimatedNode(tag)) { + if (node->setOffset(offset)) { + updatedNodeTags_.insert(node->tag()); + } + } +} + void NativeAnimatedNodesManager::stopAnimationsForNode(Tag nodeTag) { std::vector discardedAnimIds{}; diff --git a/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.h b/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.h index bcc40e7ed56..be83cf76aae 100644 --- a/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.h +++ b/packages/react-native/ReactCxxPlatform/react/renderer/animated/NativeAnimatedNodesManager.h @@ -96,6 +96,8 @@ class NativeAnimatedNodesManager { void setAnimatedNodeValue(Tag tag, double value); + void setAnimatedNodeOffset(Tag tag, double offset); + // drivers void startAnimatingNode(