first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

View File

@@ -0,0 +1,38 @@
/*
* 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.
*/
#import <CoreGraphics/CoreGraphics.h>
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
static CGFloat RCTSingleFrameInterval = (CGFloat)(1.0 / 60.0);
@class RCTValueAnimatedNode;
NS_ASSUME_NONNULL_BEGIN
@protocol RCTAnimationDriver <NSObject>
@property (nonatomic, readonly) NSNumber *animationId;
@property (nonatomic, readonly) RCTValueAnimatedNode *valueNode;
@property (nonatomic, readonly) BOOL animationHasBegun;
@property (nonatomic, readonly) BOOL animationHasFinished;
- (instancetype)initWithId:(NSNumber *)animationId
config:(NSDictionary *)config
forNode:(RCTValueAnimatedNode *)valueNode
callBack:(nullable RCTResponseSenderBlock)callback;
- (void)startAnimation;
- (void)stepAnimationWithTime:(NSTimeInterval)currentTime;
- (void)stopAnimation;
- (void)resetAnimationConfig:(NSDictionary *)config;
NS_ASSUME_NONNULL_END
@end

View File

@@ -0,0 +1,12 @@
/*
* 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.
*/
#import "RCTAnimationDriver.h"
@interface RCTDecayAnimation : NSObject <RCTAnimationDriver>
@end

View File

@@ -0,0 +1,125 @@
/*
* 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.
*/
#import <React/RCTDecayAnimation.h>
#import <React/RCTConvert.h>
#import <UIKit/UIKit.h>
#import <React/RCTAnimationUtils.h>
#import <React/RCTValueAnimatedNode.h>
@interface RCTDecayAnimation ()
@property (nonatomic, strong) NSNumber *animationId;
@property (nonatomic, strong) RCTValueAnimatedNode *valueNode;
@property (nonatomic, assign) BOOL animationHasBegun;
@property (nonatomic, assign) BOOL animationHasFinished;
@end
@implementation RCTDecayAnimation {
CGFloat _velocity;
CGFloat _deceleration;
NSTimeInterval _frameStartTime;
CGFloat _fromValue;
CGFloat _lastValue;
NSInteger _iterations;
NSInteger _currentLoop;
RCTResponseSenderBlock _callback;
}
- (instancetype)initWithId:(NSNumber *)animationId
config:(NSDictionary *)config
forNode:(RCTValueAnimatedNode *)valueNode
callBack:(nullable RCTResponseSenderBlock)callback
{
if ((self = [super init])) {
_callback = [callback copy];
_animationId = animationId;
_valueNode = valueNode;
_fromValue = 0;
_lastValue = 0;
_velocity = [RCTConvert CGFloat:config[@"velocity"]]; // initial velocity
[self resetAnimationConfig:config];
}
return self;
}
- (void)resetAnimationConfig:(NSDictionary *)config
{
NSNumber *iterations = [RCTConvert NSNumber:config[@"iterations"]] ?: @1;
_fromValue = _lastValue;
_deceleration = [RCTConvert CGFloat:config[@"deceleration"]];
_iterations = iterations.integerValue;
_currentLoop = 1;
_animationHasFinished = iterations.integerValue == 0;
}
RCT_NOT_IMPLEMENTED(-(instancetype)init)
- (void)startAnimation
{
_frameStartTime = -1;
_animationHasBegun = YES;
}
- (void)stopAnimation
{
if (_callback) {
_callback(@[ @{@"finished" : @(_animationHasFinished), @"value" : @(_valueNode.value)} ]);
}
_valueNode = nil;
}
- (void)stepAnimationWithTime:(NSTimeInterval)currentTime
{
if (!_animationHasBegun || _animationHasFinished) {
// Animation has not begun or animation has already finished.
return;
}
if (_frameStartTime == -1) {
// Since this is the first animation step, consider the start to be on the previous frame.
_frameStartTime = currentTime - RCTSingleFrameInterval;
if (_fromValue == _lastValue) {
// First iteration, assign _fromValue based on _valueNode.
_fromValue = _valueNode.value;
} else {
// Not the first iteration, reset _valueNode based on _fromValue.
[self updateValue:_fromValue];
}
_lastValue = _valueNode.value;
}
CGFloat value = _fromValue +
(_velocity / (1 - _deceleration)) *
(1 - exp(-(1 - _deceleration) * (currentTime - _frameStartTime) * 1000.0 / RCTAnimationDragCoefficient()));
[self updateValue:value];
if (fabs(_lastValue - value) < 0.1) {
if (_iterations == -1 || _currentLoop < _iterations) {
// Set _frameStartTime to -1 to reset instance variables on the next runAnimationStep.
_frameStartTime = -1;
_currentLoop++;
} else {
_animationHasFinished = true;
return;
}
}
_lastValue = value;
}
- (void)updateValue:(CGFloat)outputValue
{
_valueNode.value = outputValue;
[_valueNode setNeedsUpdate];
}
@end

View File

@@ -0,0 +1,20 @@
/*
* 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.
*/
#import <React/RCTEventDispatcherProtocol.h>
#import "RCTValueAnimatedNode.h"
@interface RCTEventAnimation : NSObject
@property (nonatomic, readonly, weak) RCTValueAnimatedNode *valueNode;
- (instancetype)initWithEventPath:(NSArray<NSString *> *)eventPath valueNode:(RCTValueAnimatedNode *)valueNode;
- (void)updateWithEvent:(id<RCTEvent>)event;
@end

View File

@@ -0,0 +1,36 @@
/*
* 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.
*/
#import <React/RCTEventAnimation.h>
@implementation RCTEventAnimation {
NSArray<NSString *> *_eventPath;
}
- (instancetype)initWithEventPath:(NSArray<NSString *> *)eventPath valueNode:(RCTValueAnimatedNode *)valueNode
{
if ((self = [super init]) != nullptr) {
_eventPath = eventPath;
_valueNode = valueNode;
}
return self;
}
- (void)updateWithEvent:(id<RCTEvent>)event
{
NSArray *args = event.arguments;
// Supported events args are in the following order: viewTag, eventName, eventData.
id currentValue = args[2];
for (NSString *key in _eventPath) {
currentValue = [currentValue valueForKey:key];
}
_valueNode.value = ((NSNumber *)currentValue).doubleValue;
[_valueNode setNeedsUpdate];
}
@end

View File

@@ -0,0 +1,12 @@
/*
* 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.
*/
#import "RCTAnimationDriver.h"
@interface RCTFrameAnimation : NSObject <RCTAnimationDriver>
@end

View File

@@ -0,0 +1,151 @@
/*
* 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.
*/
#import <React/RCTFrameAnimation.h>
#import <UIKit/UIKit.h>
#import <React/RCTConvert.h>
#import <React/RCTDefines.h>
#import <React/RCTAnimationUtils.h>
#import <React/RCTValueAnimatedNode.h>
@interface RCTFrameAnimation ()
@property (nonatomic, strong) NSNumber *animationId;
@property (nonatomic, strong) RCTValueAnimatedNode *valueNode;
@property (nonatomic, assign) BOOL animationHasBegun;
@property (nonatomic, assign) BOOL animationHasFinished;
@end
@implementation RCTFrameAnimation {
NSArray<NSNumber *> *_frames;
CGFloat _toValue;
CGFloat _fromValue;
CGFloat _lastPosition;
NSTimeInterval _animationStartTime;
NSTimeInterval _animationCurrentTime;
RCTResponseSenderBlock _callback;
NSInteger _iterations;
NSInteger _currentLoop;
}
- (instancetype)initWithId:(NSNumber *)animationId
config:(NSDictionary *)config
forNode:(RCTValueAnimatedNode *)valueNode
callBack:(nullable RCTResponseSenderBlock)callback
{
if ((self = [super init])) {
_animationId = animationId;
_lastPosition = _fromValue = valueNode.value;
_valueNode = valueNode;
_callback = [callback copy];
[self resetAnimationConfig:config];
}
return self;
}
- (void)resetAnimationConfig:(NSDictionary *)config
{
NSNumber *toValue = [RCTConvert NSNumber:config[@"toValue"]] ?: @1;
NSArray<NSNumber *> *frames = [RCTConvert NSNumberArray:config[@"frames"]];
NSNumber *iterations = [RCTConvert NSNumber:config[@"iterations"]] ?: @1;
_fromValue = _lastPosition;
_toValue = toValue.floatValue;
_frames = [frames copy];
_animationStartTime = _animationCurrentTime = -1;
_animationHasFinished = iterations.integerValue == 0;
_iterations = iterations.integerValue;
_currentLoop = 1;
}
RCT_NOT_IMPLEMENTED(-(instancetype)init)
- (void)startAnimation
{
_animationStartTime = _animationCurrentTime = -1;
_animationHasBegun = YES;
}
- (void)stopAnimation
{
if (_callback) {
_callback(@[ @{@"finished" : @(_animationHasFinished), @"value" : @(_valueNode.value)} ]);
}
_valueNode = nil;
}
- (void)stepAnimationWithTime:(NSTimeInterval)currentTime
{
if (!_animationHasBegun || _animationHasFinished || _frames.count == 0) {
// Animation has not begun or animation has already finished.
return;
}
if (_animationStartTime == -1) {
_animationStartTime = _animationCurrentTime = currentTime;
}
_animationCurrentTime = currentTime;
NSTimeInterval currentDuration = (_animationCurrentTime - _animationStartTime) / RCTAnimationDragCoefficient();
// Determine how many frames have passed since last update.
// Get index of frames that surround the current interval
NSUInteger startIndex = floor(currentDuration / RCTSingleFrameInterval);
NSUInteger nextIndex = startIndex + 1;
if (nextIndex >= _frames.count) {
if (_iterations == -1 || _currentLoop < _iterations) {
// Looping, reset to the first frame value.
_animationStartTime = currentTime;
_currentLoop++;
NSNumber *firstValue = _frames.firstObject;
[self updateOutputWithFrameOutput:firstValue.doubleValue];
} else {
_animationHasFinished = YES;
// We are at the end of the animation
// Update value and flag animation has ended.
NSNumber *finalValue = _frames.lastObject;
[self updateOutputWithFrameOutput:finalValue.doubleValue];
}
return;
}
// Do a linear remap of the two frames to safeguard against variable framerates
NSNumber *fromFrameValue = _frames[startIndex];
NSNumber *toFrameValue = _frames[nextIndex];
NSTimeInterval fromInterval = (double)startIndex * RCTSingleFrameInterval;
NSTimeInterval toInterval = (double)nextIndex * RCTSingleFrameInterval;
// Interpolate between the individual frames to ensure the animations are
// smooth and of the proper duration regardless of the framerate.
CGFloat frameOutput = RCTInterpolateValue(
currentDuration,
fromInterval,
toInterval,
fromFrameValue.doubleValue,
toFrameValue.doubleValue,
EXTRAPOLATE_TYPE_EXTEND,
EXTRAPOLATE_TYPE_EXTEND);
[self updateOutputWithFrameOutput:frameOutput];
}
- (void)updateOutputWithFrameOutput:(CGFloat)frameOutput
{
CGFloat outputValue =
RCTInterpolateValue(frameOutput, 0, 1, _fromValue, _toValue, EXTRAPOLATE_TYPE_EXTEND, EXTRAPOLATE_TYPE_EXTEND);
_lastPosition = outputValue;
_valueNode.value = outputValue;
[_valueNode setNeedsUpdate];
}
@end

View File

@@ -0,0 +1,12 @@
/*
* 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.
*/
#import "RCTAnimationDriver.h"
@interface RCTSpringAnimation : NSObject <RCTAnimationDriver>
@end

View File

@@ -0,0 +1,200 @@
/*
* 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.
*/
#import <React/RCTSpringAnimation.h>
#import <UIKit/UIKit.h>
#import <React/RCTConvert.h>
#import <React/RCTDefines.h>
#import <React/RCTAnimationUtils.h>
#import <React/RCTValueAnimatedNode.h>
@interface RCTSpringAnimation ()
@property (nonatomic, strong) NSNumber *animationId;
@property (nonatomic, strong) RCTValueAnimatedNode *valueNode;
@property (nonatomic, assign) BOOL animationHasBegun;
@property (nonatomic, assign) BOOL animationHasFinished;
@end
const NSTimeInterval MAX_DELTA_TIME = 0.064;
@implementation RCTSpringAnimation {
CGFloat _toValue;
CGFloat _fromValue;
BOOL _overshootClamping;
CGFloat _restDisplacementThreshold;
CGFloat _restSpeedThreshold;
CGFloat _stiffness;
CGFloat _damping;
CGFloat _mass;
CGFloat _initialVelocity;
NSTimeInterval _animationStartTime;
NSTimeInterval _animationCurrentTime;
RCTResponseSenderBlock _callback;
CGFloat _lastPosition;
CGFloat _lastVelocity;
NSInteger _iterations;
NSInteger _currentLoop;
NSTimeInterval _t; // Current time (startTime + dt)
}
- (instancetype)initWithId:(NSNumber *)animationId
config:(NSDictionary *)config
forNode:(RCTValueAnimatedNode *)valueNode
callBack:(nullable RCTResponseSenderBlock)callback
{
if ((self = [super init])) {
_animationId = animationId;
_lastPosition = valueNode.value;
_valueNode = valueNode;
_lastVelocity = [RCTConvert CGFloat:config[@"initialVelocity"]];
_callback = [callback copy];
[self resetAnimationConfig:config];
}
return self;
}
- (void)resetAnimationConfig:(NSDictionary *)config
{
NSNumber *iterations = [RCTConvert NSNumber:config[@"iterations"]] ?: @1;
_toValue = [RCTConvert CGFloat:config[@"toValue"]];
_overshootClamping = [RCTConvert BOOL:config[@"overshootClamping"]];
_restDisplacementThreshold = [RCTConvert CGFloat:config[@"restDisplacementThreshold"]];
_restSpeedThreshold = [RCTConvert CGFloat:config[@"restSpeedThreshold"]];
_stiffness = [RCTConvert CGFloat:config[@"stiffness"]];
_damping = [RCTConvert CGFloat:config[@"damping"]];
_mass = [RCTConvert CGFloat:config[@"mass"]];
_initialVelocity = _lastVelocity;
_fromValue = _lastPosition;
_fromValue = _lastPosition;
_lastVelocity = _initialVelocity;
_animationHasFinished = iterations.integerValue == 0;
_iterations = iterations.integerValue;
_currentLoop = 1;
_animationStartTime = _animationCurrentTime = -1;
_animationHasBegun = YES;
}
RCT_NOT_IMPLEMENTED(-(instancetype)init)
- (void)startAnimation
{
_animationStartTime = _animationCurrentTime = -1;
_animationHasBegun = YES;
}
- (void)stopAnimation
{
if (_callback) {
_callback(@[ @{@"finished" : @(_animationHasFinished), @"value" : @(_valueNode.value)} ]);
}
_valueNode = nil;
}
- (void)stepAnimationWithTime:(NSTimeInterval)currentTime
{
if (!_animationHasBegun || _animationHasFinished) {
// Animation has not begun or animation has already finished.
return;
}
// calculate delta time
if (_animationStartTime == -1) {
_t = 0.0;
_animationStartTime = currentTime;
} else {
// Handle frame drops, and only advance dt by a max of MAX_DELTA_TIME
NSTimeInterval deltaTime = MIN(MAX_DELTA_TIME, currentTime - _animationCurrentTime);
_t = _t + deltaTime / RCTAnimationDragCoefficient();
}
// store the timestamp
_animationCurrentTime = currentTime;
CGFloat c = _damping;
CGFloat m = _mass;
CGFloat k = _stiffness;
CGFloat v0 = -_initialVelocity;
CGFloat zeta = c / (2 * sqrtf(k * m));
CGFloat omega0 = sqrtf(k / m);
CGFloat omega1 = omega0 * sqrtf(1.0 - (zeta * zeta));
CGFloat x0 = _toValue - _fromValue;
CGFloat position;
CGFloat velocity;
if (zeta < 1) {
// Under damped
CGFloat envelope = expf(-zeta * omega0 * _t);
position = _toValue - envelope * ((v0 + zeta * omega0 * x0) / omega1 * sinf(omega1 * _t) + x0 * cosf(omega1 * _t));
// This looks crazy -- it's actually just the derivative of the
// oscillation function
velocity =
zeta * omega0 * envelope * (sinf(omega1 * _t) * (v0 + zeta * omega0 * x0) / omega1 + x0 * cosf(omega1 * _t)) -
envelope * (cosf(omega1 * _t) * (v0 + zeta * omega0 * x0) - omega1 * x0 * sinf(omega1 * _t));
} else {
CGFloat envelope = expf(-omega0 * _t);
position = _toValue - envelope * (x0 + (v0 + omega0 * x0) * _t);
velocity = envelope * (v0 * (_t * omega0 - 1) + _t * x0 * (omega0 * omega0));
}
_lastPosition = position;
_lastVelocity = velocity;
[self onUpdate:position];
// Conditions for stopping the spring animation
BOOL isOvershooting = NO;
if (_overshootClamping && _stiffness != 0) {
if (_fromValue < _toValue) {
isOvershooting = position > _toValue;
} else {
isOvershooting = position < _toValue;
}
}
BOOL isVelocity = ABS(velocity) <= _restSpeedThreshold;
BOOL isDisplacement = YES;
if (_stiffness != 0) {
isDisplacement = ABS(_toValue - position) <= _restDisplacementThreshold;
}
if (isOvershooting || (isVelocity && isDisplacement)) {
if (_stiffness != 0) {
// Ensure that we end up with a round value
if (_animationHasFinished) {
return;
}
[self onUpdate:_toValue];
}
if (_iterations == -1 || _currentLoop < _iterations) {
_lastPosition = _fromValue;
_lastVelocity = _initialVelocity;
// Set _animationStartTime to -1 to reset instance variables on the next animation step.
_animationStartTime = -1;
_currentLoop++;
[self onUpdate:_fromValue];
} else {
_animationHasFinished = YES;
}
}
}
- (void)onUpdate:(CGFloat)outputValue
{
_valueNode.value = outputValue;
[_valueNode setNeedsUpdate];
}
@end

View File

@@ -0,0 +1,12 @@
/*
* 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.
*/
#import "RCTValueAnimatedNode.h"
@interface RCTAdditionAnimatedNode : RCTValueAnimatedNode
@end

View File

@@ -0,0 +1,25 @@
/*
* 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.
*/
#import <React/RCTAdditionAnimatedNode.h>
@implementation RCTAdditionAnimatedNode
- (void)performUpdate
{
[super performUpdate];
NSArray<NSNumber *> *inputNodes = self.config[@"input"];
if (inputNodes.count > 1) {
RCTValueAnimatedNode *parent1 = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:inputNodes[0]];
RCTValueAnimatedNode *parent2 = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:inputNodes[1]];
if ([parent1 isKindOfClass:[RCTValueAnimatedNode class]] && [parent2 isKindOfClass:[RCTValueAnimatedNode class]]) {
self.value = parent1.value + parent2.value;
}
}
}
@end

View File

@@ -0,0 +1,50 @@
/*
* 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.
*/
#import <Foundation/Foundation.h>
@class RCTNativeAnimatedNodesManager;
@interface RCTAnimatedNode : NSObject
- (instancetype)initWithTag:(NSNumber *)tag config:(NSDictionary<NSString *, id> *)config NS_DESIGNATED_INITIALIZER;
@property (nonatomic, readonly) NSNumber *nodeTag;
@property (nonatomic, weak) RCTNativeAnimatedNodesManager *manager;
@property (nonatomic, copy, readonly) NSDictionary<NSString *, id> *config;
@property (nonatomic, copy, readonly) NSMapTable<NSNumber *, RCTAnimatedNode *> *childNodes;
@property (nonatomic, copy, readonly) NSMapTable<NSNumber *, RCTAnimatedNode *> *parentNodes;
@property (nonatomic, readonly) BOOL needsUpdate;
- (BOOL)isManagedByFabric;
/**
* Marks a node and its children as needing update.
*/
- (void)setNeedsUpdate NS_REQUIRES_SUPER;
/**
* The node will update its value if necessary and only after its parents have updated.
*/
- (void)updateNodeIfNecessary NS_REQUIRES_SUPER;
/**
* Where the actual update code lives. Called internally from updateNodeIfNecessary
*/
- (void)performUpdate NS_REQUIRES_SUPER;
- (void)addChild:(RCTAnimatedNode *)child NS_REQUIRES_SUPER;
- (void)removeChild:(RCTAnimatedNode *)child NS_REQUIRES_SUPER;
- (void)onAttachedToNode:(RCTAnimatedNode *)parent NS_REQUIRES_SUPER;
- (void)onDetachedFromNode:(RCTAnimatedNode *)parent NS_REQUIRES_SUPER;
- (void)detachNode NS_REQUIRES_SUPER;
@end

View File

@@ -0,0 +1,126 @@
/*
* 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.
*/
#import <React/RCTAnimatedNode.h>
#import <React/RCTDefines.h>
@implementation RCTAnimatedNode {
NSMapTable<NSNumber *, RCTAnimatedNode *> *_childNodes;
NSMapTable<NSNumber *, RCTAnimatedNode *> *_parentNodes;
}
- (instancetype)initWithTag:(NSNumber *)tag config:(NSDictionary<NSString *, id> *)config
{
if ((self = [super init]) != nullptr) {
_nodeTag = tag;
_config = [config copy];
}
return self;
}
RCT_NOT_IMPLEMENTED(-(instancetype)init)
- (NSMapTable<NSNumber *, RCTAnimatedNode *> *)childNodes
{
return _childNodes;
}
- (NSMapTable<NSNumber *, RCTAnimatedNode *> *)parentNodes
{
return _parentNodes;
}
- (void)addChild:(RCTAnimatedNode *)child
{
if (_childNodes == nullptr) {
_childNodes = [NSMapTable strongToWeakObjectsMapTable];
}
if (child != nullptr) {
[_childNodes setObject:child forKey:child.nodeTag];
[child onAttachedToNode:self];
}
}
- (void)removeChild:(RCTAnimatedNode *)child
{
if (_childNodes == nullptr) {
return;
}
if (child != nullptr) {
[_childNodes removeObjectForKey:child.nodeTag];
[child onDetachedFromNode:self];
}
}
- (void)onAttachedToNode:(RCTAnimatedNode *)parent
{
if (_parentNodes == nullptr) {
_parentNodes = [NSMapTable strongToWeakObjectsMapTable];
}
if (parent != nullptr) {
[_parentNodes setObject:parent forKey:parent.nodeTag];
}
}
- (void)onDetachedFromNode:(RCTAnimatedNode *)parent
{
if (_parentNodes == nullptr) {
return;
}
if (parent != nullptr) {
[_parentNodes removeObjectForKey:parent.nodeTag];
}
}
- (void)detachNode
{
for (RCTAnimatedNode *parent in _parentNodes.objectEnumerator) {
[parent removeChild:self];
}
for (RCTAnimatedNode *child in _childNodes.objectEnumerator) {
[self removeChild:child];
}
}
- (void)setNeedsUpdate
{
_needsUpdate = YES;
for (RCTAnimatedNode *child in _childNodes.objectEnumerator) {
[child setNeedsUpdate];
}
}
- (void)updateNodeIfNecessary
{
if (_needsUpdate) {
for (RCTAnimatedNode *parent in _parentNodes.objectEnumerator) {
[parent updateNodeIfNecessary];
}
[self performUpdate];
}
}
- (void)performUpdate
{
_needsUpdate = NO;
// To be overridden by subclasses
// This method is called on a node only if it has been marked for update
// during the current update loop
}
- (BOOL)isManagedByFabric
{
for (RCTAnimatedNode *child in _childNodes.objectEnumerator) {
if ([child isManagedByFabric]) {
return YES;
}
}
return NO;
}
@end

View File

@@ -0,0 +1,14 @@
/*
* 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.
*/
#import <React/RCTAnimatedNode.h>
@interface RCTColorAnimatedNode : RCTAnimatedNode
@property (nonatomic, assign) int32_t color;
@end

View File

@@ -0,0 +1,29 @@
/*
* 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.
*/
#import <React/RCTColorAnimatedNode.h>
#import <React/RCTValueAnimatedNode.h>
#import <React/RCTAnimationUtils.h>
@implementation RCTColorAnimatedNode
- (void)performUpdate
{
[super performUpdate];
RCTValueAnimatedNode *rNode = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:self.config[@"r"]];
RCTValueAnimatedNode *gNode = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:self.config[@"g"]];
RCTValueAnimatedNode *bNode = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:self.config[@"b"]];
RCTValueAnimatedNode *aNode = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:self.config[@"a"]];
_color = RCTColorFromComponents(rNode.value, gNode.value, bNode.value, aNode.value);
// TODO (T111179606): Support platform colors for color animations
}
@end

View File

@@ -0,0 +1,12 @@
/*
* 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.
*/
#import "RCTValueAnimatedNode.h"
@interface RCTDiffClampAnimatedNode : RCTValueAnimatedNode
@end

View File

@@ -0,0 +1,64 @@
/*
* 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.
*/
#import <React/RCTDiffClampAnimatedNode.h>
#import <React/RCTLog.h>
@implementation RCTDiffClampAnimatedNode {
NSNumber *_inputNodeTag;
CGFloat _min;
CGFloat _max;
CGFloat _lastValue;
}
- (instancetype)initWithTag:(NSNumber *)tag config:(NSDictionary<NSString *, id> *)config
{
if (self = [super initWithTag:tag config:config]) {
_inputNodeTag = config[@"input"];
_min = [config[@"min"] floatValue];
_max = [config[@"max"] floatValue];
}
return self;
}
- (void)onAttachedToNode:(RCTAnimatedNode *)parent
{
[super onAttachedToNode:parent];
self.value = _lastValue = [self inputNodeValue];
}
- (void)performUpdate
{
[super performUpdate];
CGFloat value = [self inputNodeValue];
CGFloat diff = value - _lastValue;
_lastValue = value;
self.value = MIN(MAX(self.value + diff, _min), _max);
}
- (CGFloat)inputNodeValue
{
if (self.parentNodes == nil) {
RCTLogWarn(@"Animated.DiffClamp node has not been fully initialised.");
return 0;
}
RCTValueAnimatedNode *inputNode = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:_inputNodeTag];
if (![inputNode isKindOfClass:[RCTValueAnimatedNode class]]) {
RCTLogError(@"Illegal node ID set as an input for Animated.DiffClamp node");
return 0;
}
return inputNode.value;
}
@end

View File

@@ -0,0 +1,12 @@
/*
* 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.
*/
#import "RCTValueAnimatedNode.h"
@interface RCTDivisionAnimatedNode : RCTValueAnimatedNode
@end

View File

@@ -0,0 +1,32 @@
/*
* 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.
*/
#import <React/RCTDivisionAnimatedNode.h>
#import <React/RCTLog.h>
@implementation RCTDivisionAnimatedNode
- (void)performUpdate
{
[super performUpdate];
NSArray<NSNumber *> *inputNodes = self.config[@"input"];
if (inputNodes.count > 1) {
RCTValueAnimatedNode *parent1 = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:inputNodes[0]];
RCTValueAnimatedNode *parent2 = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:inputNodes[1]];
if ([parent1 isKindOfClass:[RCTValueAnimatedNode class]] && [parent2 isKindOfClass:[RCTValueAnimatedNode class]]) {
if (parent2.value == 0) {
RCTLogError(@"Detected a division by zero in Animated.divide node");
return;
}
self.value = parent1.value / parent2.value;
}
}
}
@end

View File

@@ -0,0 +1,26 @@
/*
* 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.
*/
#import "RCTValueAnimatedNode.h"
#import <React/RCTDefines.h>
NS_ASSUME_NONNULL_BEGIN
RCT_EXTERN NSString *RCTInterpolateString(
NSString *pattern,
CGFloat inputValue,
NSArray<NSNumber *> *inputRange,
NSArray<NSArray<NSNumber *> *> *outputRange,
NSString *extrapolateLeft,
NSString *extrapolateRight);
@interface RCTInterpolationAnimatedNode : RCTValueAnimatedNode
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,169 @@
/*
* 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.
*/
#import "RCTInterpolationAnimatedNode.h"
#import <React/RCTAnimationUtils.h>
#import <React/RCTConvert.h>
typedef NS_ENUM(NSInteger, RCTInterpolationOutputType) {
RCTInterpolationOutputNumber,
RCTInterpolationOutputColor,
RCTInterpolationOutputString,
};
static NSRegularExpression *getNumericComponentRegex(void)
{
static NSRegularExpression *regex;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *fpRegex = @"[+-]?(\\d+\\.?\\d*|\\.\\d+)([eE][+-]?\\d+)?";
regex = [NSRegularExpression regularExpressionWithPattern:fpRegex
options:NSRegularExpressionCaseInsensitive
error:nil];
});
return regex;
}
static NSArray<NSArray<NSNumber *> *> *outputFromStringPattern(NSString *input)
{
NSMutableArray *output = [NSMutableArray array];
[getNumericComponentRegex()
enumerateMatchesInString:input
options:0
range:NSMakeRange(0, input.length)
usingBlock:^(NSTextCheckingResult *_Nullable result, NSMatchingFlags flags, BOOL *_Nonnull stop) {
[output addObject:@([[input substringWithRange:result.range] doubleValue])];
}];
return output;
}
NSString *RCTInterpolateString(
NSString *pattern,
CGFloat inputValue,
NSArray<NSNumber *> *inputRange,
NSArray<NSArray<NSNumber *> *> *outputRange,
NSString *extrapolateLeft,
NSString *extrapolateRight)
{
NSUInteger rangeIndex = RCTFindIndexOfNearestValue(inputValue, inputRange);
NSMutableString *output = [NSMutableString stringWithString:pattern];
NSArray<NSTextCheckingResult *> *matches =
[getNumericComponentRegex() matchesInString:pattern options:0 range:NSMakeRange(0, pattern.length)];
NSInteger matchIndex = matches.count - 1;
for (NSTextCheckingResult *match in [matches reverseObjectEnumerator]) {
CGFloat val = RCTInterpolateValue(
inputValue,
[inputRange[rangeIndex] doubleValue],
[inputRange[rangeIndex + 1] doubleValue],
[outputRange[rangeIndex][matchIndex] doubleValue],
[outputRange[rangeIndex + 1][matchIndex] doubleValue],
extrapolateLeft,
extrapolateRight);
[output replaceCharactersInRange:match.range withString:[@(val) stringValue]];
matchIndex--;
}
return output;
}
@implementation RCTInterpolationAnimatedNode {
__weak RCTValueAnimatedNode *_parentNode;
NSArray<NSNumber *> *_inputRange;
NSArray *_outputRange;
NSString *_extrapolateLeft;
NSString *_extrapolateRight;
RCTInterpolationOutputType _outputType;
id _Nullable _outputvalue;
NSString *_Nullable _outputPattern;
NSArray<NSTextCheckingResult *> *_matches;
}
- (instancetype)initWithTag:(NSNumber *)tag config:(NSDictionary<NSString *, id> *)config
{
if ((self = [super initWithTag:tag config:config]) != nullptr) {
_inputRange = config[@"inputRange"];
NSArray *outputRangeConfig = config[@"outputRange"];
if ([config[@"outputType"] isEqual:@"color"]) {
_outputType = RCTInterpolationOutputColor;
} else if ([outputRangeConfig[0] isKindOfClass:[NSString class]]) {
_outputType = RCTInterpolationOutputString;
_outputPattern = outputRangeConfig[0];
} else {
_outputType = RCTInterpolationOutputNumber;
}
NSMutableArray *outputRange = [NSMutableArray arrayWithCapacity:outputRangeConfig.count];
for (id value in outputRangeConfig) {
switch (_outputType) {
case RCTInterpolationOutputColor: {
UIColor *color = [RCTConvert UIColor:value];
[outputRange addObject:(color != nullptr) ? color : [UIColor whiteColor]];
break;
}
case RCTInterpolationOutputString:
[outputRange addObject:outputFromStringPattern(value)];
break;
case RCTInterpolationOutputNumber:
[outputRange addObject:value];
break;
}
}
_outputRange = outputRange;
_extrapolateLeft = config[@"extrapolateLeft"];
_extrapolateRight = config[@"extrapolateRight"];
}
return self;
}
- (void)onAttachedToNode:(RCTAnimatedNode *)parent
{
[super onAttachedToNode:parent];
if ([parent isKindOfClass:[RCTValueAnimatedNode class]]) {
_parentNode = (RCTValueAnimatedNode *)parent;
}
}
- (void)onDetachedFromNode:(RCTAnimatedNode *)parent
{
[super onDetachedFromNode:parent];
if (_parentNode == parent) {
_parentNode = nil;
}
}
- (void)performUpdate
{
[super performUpdate];
if (_parentNode == nullptr) {
return;
}
CGFloat inputValue = _parentNode.value;
switch (_outputType) {
case RCTInterpolationOutputColor:
_outputvalue = @(RCTInterpolateColorInRange(inputValue, _inputRange, _outputRange));
break;
case RCTInterpolationOutputString:
_outputvalue = RCTInterpolateString(
_outputPattern, inputValue, _inputRange, _outputRange, _extrapolateLeft, _extrapolateRight);
break;
case RCTInterpolationOutputNumber:
self.value =
RCTInterpolateValueInRange(inputValue, _inputRange, _outputRange, _extrapolateLeft, _extrapolateRight);
break;
}
}
- (id)animatedObject
{
return _outputvalue;
}
@end

View File

@@ -0,0 +1,12 @@
/*
* 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.
*/
#import "RCTValueAnimatedNode.h"
@interface RCTModuloAnimatedNode : RCTValueAnimatedNode
@end

View File

@@ -0,0 +1,22 @@
/*
* 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.
*/
#import <React/RCTModuloAnimatedNode.h>
@implementation RCTModuloAnimatedNode
- (void)performUpdate
{
[super performUpdate];
NSNumber *inputNode = self.config[@"input"];
NSNumber *modulus = self.config[@"modulus"];
RCTValueAnimatedNode *parent = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:inputNode];
const float m = modulus.floatValue;
self.value = fmodf(fmodf(parent.value, m) + m, m);
}
@end

View File

@@ -0,0 +1,12 @@
/*
* 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.
*/
#import "RCTValueAnimatedNode.h"
@interface RCTMultiplicationAnimatedNode : RCTValueAnimatedNode
@end

View File

@@ -0,0 +1,26 @@
/*
* 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.
*/
#import <React/RCTMultiplicationAnimatedNode.h>
@implementation RCTMultiplicationAnimatedNode
- (void)performUpdate
{
[super performUpdate];
NSArray<NSNumber *> *inputNodes = self.config[@"input"];
if (inputNodes.count > 1) {
RCTValueAnimatedNode *parent1 = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:inputNodes[0]];
RCTValueAnimatedNode *parent2 = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:inputNodes[1]];
if ([parent1 isKindOfClass:[RCTValueAnimatedNode class]] && [parent2 isKindOfClass:[RCTValueAnimatedNode class]]) {
self.value = parent1.value * parent2.value;
}
}
}
@end

View File

@@ -0,0 +1,14 @@
/*
* 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.
*/
#import "RCTAnimatedNode.h"
@interface RCTObjectAnimatedNode : RCTAnimatedNode
@property (nonatomic, strong, readonly) id value;
@end

View File

@@ -0,0 +1,66 @@
/*
* 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.
*/
#import "RCTObjectAnimatedNode.h"
#import "RCTValueAnimatedNode.h"
NSString *const VALUE_KEY = @"value";
NSString *const NODE_TAG_KEY = @"nodeTag";
@implementation RCTObjectAnimatedNode
- (void)performUpdate
{
[super performUpdate];
id value = self.config[VALUE_KEY];
if ([value isKindOfClass:[NSDictionary class]]) {
_value = [self _performUpdateHelperDictionary:(NSDictionary *)value];
} else if ([value isKindOfClass:[NSArray class]]) {
_value = [self _performUpdateHelperArray:(NSArray *)value];
}
}
- (NSDictionary<NSString *, id> *)_performUpdateHelperDictionary:(NSDictionary<NSString *, id> *)source
{
NSMutableDictionary<NSString *, id> *result = [NSMutableDictionary new];
for (NSString *key in source) {
result[key] = [self _convertValue:source[key]];
}
return result;
}
- (NSArray *)_performUpdateHelperArray:(NSArray *)source
{
NSMutableArray *result = [NSMutableArray array];
for (id value in source) {
[result addObject:[self _convertValue:value]];
}
return result;
}
- (id)_convertValue:(id)value
{
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary<NSString *, id> *dict = (NSDictionary *)value;
id nodeTag = [dict objectForKey:NODE_TAG_KEY];
if ((nodeTag != nullptr) && [nodeTag isKindOfClass:[NSNumber class]]) {
RCTAnimatedNode *node = [self.parentNodes objectForKey:(NSNumber *)nodeTag];
if ([node isKindOfClass:[RCTValueAnimatedNode class]]) {
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
return @(valueNode.value);
}
}
return [self _performUpdateHelperDictionary:dict];
} else if ([value isKindOfClass:[NSArray class]]) {
return [self _performUpdateHelperArray:(NSArray *)value];
} else {
return value;
}
}
@end

View File

@@ -0,0 +1,26 @@
/*
* 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.
*/
#import "RCTAnimatedNode.h"
#import <React/RCTSurfacePresenterStub.h>
@class RCTBridge;
@class RCTViewPropertyMapper;
@interface RCTPropsAnimatedNode : RCTAnimatedNode
- (void)connectToView:(NSNumber *)viewTag
viewName:(NSString *)viewName
bridge:(RCTBridge *)bridge
surfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter;
- (void)disconnectFromView:(NSNumber *)viewTag;
- (void)restoreDefaultValues;
@end

View File

@@ -0,0 +1,146 @@
/*
* 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.
*/
#import <React/RCTPropsAnimatedNode.h>
#import <React/RCTAnimationUtils.h>
#import <React/RCTColorAnimatedNode.h>
#import <React/RCTObjectAnimatedNode.h>
#import <React/RCTStyleAnimatedNode.h>
#import <React/RCTUIManager.h>
#import <React/RCTValueAnimatedNode.h>
@implementation RCTPropsAnimatedNode {
NSNumber *_connectedViewTag;
NSString *_connectedViewName;
__weak RCTBridge *_bridge;
__weak id<RCTSurfacePresenterStub> _surfacePresenter;
NSMutableDictionary<NSString *, NSObject *> *_propsDictionary; // TODO: use RawProps or folly::dynamic directly
BOOL _managedByFabric;
}
- (instancetype)initWithTag:(NSNumber *)tag config:(NSDictionary<NSString *, id> *)config
{
if (self = [super initWithTag:tag config:config]) {
_propsDictionary = [NSMutableDictionary new];
}
return self;
}
- (BOOL)isManagedByFabric
{
return _managedByFabric;
}
- (void)connectToView:(NSNumber *)viewTag
viewName:(NSString *)viewName
bridge:(RCTBridge *)bridge
surfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter
{
_bridge = bridge;
_surfacePresenter = surfacePresenter;
_connectedViewTag = viewTag;
_connectedViewName = viewName;
_managedByFabric = RCTUIManagerTypeForTagIsFabric(viewTag);
}
- (void)disconnectFromView:(NSNumber *)viewTag
{
_bridge = nil;
_surfacePresenter = nil;
_connectedViewTag = nil;
_connectedViewName = nil;
_managedByFabric = NO;
}
- (void)updateView
{
if (_managedByFabric) {
if (_bridge.surfacePresenter) {
[_bridge.surfacePresenter synchronouslyUpdateViewOnUIThread:_connectedViewTag props:_propsDictionary];
} else {
[_surfacePresenter synchronouslyUpdateViewOnUIThread:_connectedViewTag props:_propsDictionary];
}
} else {
[_bridge.uiManager synchronouslyUpdateViewOnUIThread:_connectedViewTag
viewName:_connectedViewName
props:_propsDictionary];
}
}
- (void)restoreDefaultValues
{
if (_managedByFabric) {
// Restoring to default values causes render of inconsistent state
// to the user because it isn't synchronised with Fabric's UIManager.
return;
}
// Restore the default value for all props that were modified by this node.
for (NSString *key in _propsDictionary.allKeys) {
_propsDictionary[key] = [NSNull null];
}
if (_propsDictionary.count) {
[self updateView];
}
}
- (NSString *)propertyNameForParentTag:(NSNumber *)parentTag
{
__block NSString *propertyName;
[self.config[@"props"]
enumerateKeysAndObjectsUsingBlock:^(NSString *_Nonnull property, NSNumber *_Nonnull tag, BOOL *_Nonnull stop) {
if ([tag isEqualToNumber:parentTag]) {
propertyName = property;
*stop = YES;
}
}];
return propertyName;
}
- (void)performUpdate
{
[super performUpdate];
// Since we are updating nodes after detaching them from views there is a time where it's
// possible that the view was disconnected and still receive an update, this is normal and we can
// simply skip that update.
if (!_connectedViewTag) {
return;
}
for (NSNumber *parentTag in self.parentNodes.keyEnumerator) {
RCTAnimatedNode *parentNode = [self.parentNodes objectForKey:parentTag];
if ([parentNode isKindOfClass:[RCTStyleAnimatedNode class]]) {
RCTStyleAnimatedNode *styleAnimatedNode = (RCTStyleAnimatedNode *)parentNode;
[_propsDictionary addEntriesFromDictionary:styleAnimatedNode.propsDictionary];
} else if ([parentNode isKindOfClass:[RCTValueAnimatedNode class]]) {
RCTValueAnimatedNode *valueAnimatedNode = (RCTValueAnimatedNode *)parentNode;
NSString *property = [self propertyNameForParentTag:parentTag];
id animatedObject = valueAnimatedNode.animatedObject;
if (animatedObject) {
_propsDictionary[property] = animatedObject;
} else {
_propsDictionary[property] = @(valueAnimatedNode.value);
}
} else if ([parentNode isKindOfClass:[RCTColorAnimatedNode class]]) {
RCTColorAnimatedNode *colorAnimatedNode = (RCTColorAnimatedNode *)parentNode;
NSString *property = [self propertyNameForParentTag:parentTag];
_propsDictionary[property] = @(colorAnimatedNode.color);
} else if ([parentNode isKindOfClass:[RCTObjectAnimatedNode class]]) {
RCTObjectAnimatedNode *objectAnimatedNode = (RCTObjectAnimatedNode *)parentNode;
NSString *property = [self propertyNameForParentTag:parentTag];
_propsDictionary[property] = objectAnimatedNode.value;
}
}
if (_propsDictionary.count) {
[self updateView];
}
}
@end

View File

@@ -0,0 +1,14 @@
/*
* 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.
*/
#import "RCTAnimatedNode.h"
@interface RCTStyleAnimatedNode : RCTAnimatedNode
- (NSDictionary<NSString *, NSObject *> *)propsDictionary;
@end

View File

@@ -0,0 +1,62 @@
/*
* 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.
*/
#import <React/RCTAnimationUtils.h>
#import <React/RCTColorAnimatedNode.h>
#import <React/RCTObjectAnimatedNode.h>
#import <React/RCTStyleAnimatedNode.h>
#import <React/RCTTransformAnimatedNode.h>
#import <React/RCTValueAnimatedNode.h>
@implementation RCTStyleAnimatedNode {
NSMutableDictionary<NSString *, NSObject *> *_propsDictionary;
}
- (instancetype)initWithTag:(NSNumber *)tag config:(NSDictionary<NSString *, id> *)config
{
if ((self = [super initWithTag:tag config:config]) != nullptr) {
_propsDictionary = [NSMutableDictionary new];
}
return self;
}
- (NSDictionary *)propsDictionary
{
return _propsDictionary;
}
- (void)performUpdate
{
[super performUpdate];
NSDictionary<NSString *, NSNumber *> *style = self.config[@"style"];
[style enumerateKeysAndObjectsUsingBlock:^(NSString *property, NSNumber *nodeTag, __unused BOOL *stop) {
RCTAnimatedNode *node = [self.parentNodes objectForKey:nodeTag];
if (node != nullptr) {
if ([node isKindOfClass:[RCTValueAnimatedNode class]]) {
RCTValueAnimatedNode *valueAnimatedNode = (RCTValueAnimatedNode *)node;
id animatedObject = valueAnimatedNode.animatedObject;
if (animatedObject != nullptr) {
_propsDictionary[property] = animatedObject;
} else {
_propsDictionary[property] = @(valueAnimatedNode.value);
}
} else if ([node isKindOfClass:[RCTTransformAnimatedNode class]]) {
RCTTransformAnimatedNode *transformAnimatedNode = (RCTTransformAnimatedNode *)node;
[_propsDictionary addEntriesFromDictionary:transformAnimatedNode.propsDictionary];
} else if ([node isKindOfClass:[RCTColorAnimatedNode class]]) {
RCTColorAnimatedNode *colorAnimatedNode = (RCTColorAnimatedNode *)node;
_propsDictionary[property] = @(colorAnimatedNode.color);
} else if ([node isKindOfClass:[RCTObjectAnimatedNode class]]) {
RCTObjectAnimatedNode *objectAnimatedNode = (RCTObjectAnimatedNode *)node;
_propsDictionary[property] = objectAnimatedNode.value;
}
}
}];
}
@end

View File

@@ -0,0 +1,12 @@
/*
* 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.
*/
#import "RCTValueAnimatedNode.h"
@interface RCTSubtractionAnimatedNode : RCTValueAnimatedNode
@end

View File

@@ -0,0 +1,25 @@
/*
* 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.
*/
#import <React/RCTSubtractionAnimatedNode.h>
@implementation RCTSubtractionAnimatedNode
- (void)performUpdate
{
[super performUpdate];
NSArray<NSNumber *> *inputNodes = self.config[@"input"];
if (inputNodes.count > 1) {
RCTValueAnimatedNode *parent1 = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:inputNodes[0]];
RCTValueAnimatedNode *parent2 = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:inputNodes[1]];
if ([parent1 isKindOfClass:[RCTValueAnimatedNode class]] && [parent2 isKindOfClass:[RCTValueAnimatedNode class]]) {
self.value = parent1.value - parent2.value;
}
}
}
@end

View File

@@ -0,0 +1,12 @@
/*
* 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.
*/
#import "RCTAnimatedNode.h"
@interface RCTTrackingAnimatedNode : RCTAnimatedNode
@end

View File

@@ -0,0 +1,47 @@
/*
* 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.
*/
#import <React/RCTNativeAnimatedNodesManager.h>
#import <React/RCTTrackingAnimatedNode.h>
#import <React/RCTValueAnimatedNode.h>
@implementation RCTTrackingAnimatedNode {
NSNumber *_animationId;
NSNumber *_toValueNodeTag;
NSNumber *_valueNodeTag;
NSMutableDictionary *_animationConfig;
}
- (instancetype)initWithTag:(NSNumber *)tag config:(NSDictionary<NSString *, id> *)config
{
if ((self = [super initWithTag:tag config:config]) != nullptr) {
_animationId = config[@"animationId"];
_toValueNodeTag = config[@"toValue"];
_valueNodeTag = config[@"value"];
_animationConfig = [NSMutableDictionary dictionaryWithDictionary:config[@"animationConfig"]];
}
return self;
}
- (void)onDetachedFromNode:(RCTAnimatedNode *)parent
{
[self.manager stopAnimation:_animationId];
[super onDetachedFromNode:parent];
}
- (void)performUpdate
{
[super performUpdate];
// change animation config's "toValue" to reflect updated value of the parent node
RCTValueAnimatedNode *node = (RCTValueAnimatedNode *)[self.parentNodes objectForKey:_toValueNodeTag];
_animationConfig[@"toValue"] = @(node.value);
[self.manager startAnimatingNode:_animationId nodeTag:_valueNodeTag config:_animationConfig endCallback:nil];
}
@end

View File

@@ -0,0 +1,14 @@
/*
* 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.
*/
#import "RCTAnimatedNode.h"
@interface RCTTransformAnimatedNode : RCTAnimatedNode
- (NSDictionary<NSString *, NSObject *> *)propsDictionary;
@end

View File

@@ -0,0 +1,55 @@
/*
* 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.
*/
#import <React/RCTTransformAnimatedNode.h>
#import <React/RCTValueAnimatedNode.h>
@implementation RCTTransformAnimatedNode {
NSMutableDictionary<NSString *, NSObject *> *_propsDictionary;
}
- (instancetype)initWithTag:(NSNumber *)tag config:(NSDictionary<NSString *, id> *)config
{
if ((self = [super initWithTag:tag config:config]) != nullptr) {
_propsDictionary = [NSMutableDictionary new];
}
return self;
}
- (NSDictionary *)propsDictionary
{
return _propsDictionary;
}
- (void)performUpdate
{
[super performUpdate];
NSArray<NSDictionary *> *transformConfigs = self.config[@"transforms"];
NSMutableArray<NSDictionary *> *transform = [NSMutableArray arrayWithCapacity:transformConfigs.count];
for (NSDictionary *transformConfig in transformConfigs) {
NSString *type = transformConfig[@"type"];
NSString *property = transformConfig[@"property"];
NSNumber *value;
if ([type isEqualToString:@"animated"]) {
NSNumber *nodeTag = transformConfig[@"nodeTag"];
RCTAnimatedNode *node = [self.parentNodes objectForKey:nodeTag];
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
continue;
}
RCTValueAnimatedNode *parentNode = (RCTValueAnimatedNode *)node;
value = @(parentNode.value);
} else {
value = transformConfig[@"value"];
}
[transform addObject:@{property : value}];
}
_propsDictionary[@"transform"] = transform;
}
@end

View File

@@ -0,0 +1,30 @@
/*
* 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.
*/
#import <UIKit/UIKit.h>
#import "RCTAnimatedNode.h"
@class RCTValueAnimatedNode;
@protocol RCTValueAnimatedNodeObserver <NSObject>
- (void)animatedNode:(RCTValueAnimatedNode *)node didUpdateValue:(CGFloat)value;
@end
@interface RCTValueAnimatedNode : RCTAnimatedNode
- (void)setOffset:(CGFloat)offset;
- (void)flattenOffset;
- (void)extractOffset;
@property (nonatomic, assign) CGFloat value;
@property (nonatomic, strong, readonly) id animatedObject;
@property (nonatomic, weak) id<RCTValueAnimatedNodeObserver> valueObserver;
@end

View File

@@ -0,0 +1,60 @@
/*
* 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.
*/
#import <React/RCTValueAnimatedNode.h>
@interface RCTValueAnimatedNode ()
@property (nonatomic, assign) CGFloat offset;
@end
@implementation RCTValueAnimatedNode
@synthesize value = _value;
- (instancetype)initWithTag:(NSNumber *)tag config:(NSDictionary<NSString *, id> *)config
{
if (self = [super initWithTag:tag config:config]) {
_offset = [self.config[@"offset"] floatValue];
_value = [self.config[@"value"] floatValue];
}
return self;
}
- (void)flattenOffset
{
_value += _offset;
_offset = 0;
}
- (void)extractOffset
{
_offset += _value;
_value = 0;
}
- (CGFloat)value
{
return _value + _offset;
}
- (id)animatedObject
{
return nil;
}
- (void)setValue:(CGFloat)value
{
_value = value;
if (_valueObserver) {
[_valueObserver animatedNode:self didUpdateValue:_value];
}
}
@end

View File

@@ -0,0 +1,41 @@
/**
* 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.
*
* @generated by an internal plugin build system
*/
#ifdef RN_DISABLE_OSS_PLUGIN_HEADER
// FB Internal: FBRCTAnimationPlugins.h is autogenerated by the build system.
#import <React/FBRCTAnimationPlugins.h>
#else
// OSS-compatibility layer
#import <Foundation/Foundation.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wreturn-type-c-linkage"
#ifdef __cplusplus
extern "C" {
#endif
// RCTTurboModuleManagerDelegate should call this to resolve module classes.
Class RCTAnimationClassProvider(const char *name);
// Lookup functions
Class RCTNativeAnimatedModuleCls(void) __attribute__((used));
Class RCTNativeAnimatedTurboModuleCls(void) __attribute__((used));
#ifdef __cplusplus
}
#endif
#pragma GCC diagnostic pop
#endif // RN_DISABLE_OSS_PLUGIN_HEADER

View File

@@ -0,0 +1,34 @@
/**
* 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.
*
* @generated by an internal plugin build system
*/
#ifndef RN_DISABLE_OSS_PLUGIN_HEADER
// OSS-compatibility layer
#import "RCTAnimationPlugins.h"
#import <string>
#import <unordered_map>
Class RCTAnimationClassProvider(const char *name) {
// Intentionally leak to avoid crashing after static destructors are run.
static const auto sCoreModuleClassMap = new const std::unordered_map<std::string, Class (*)(void)>{
{"NativeAnimatedModule", RCTNativeAnimatedModuleCls},
{"NativeAnimatedTurboModule", RCTNativeAnimatedTurboModuleCls},
};
auto p = sCoreModuleClassMap->find(name);
if (p != sCoreModuleClassMap->end()) {
auto classFunc = p->second;
return classFunc();
}
return nil;
}
#endif // RN_DISABLE_OSS_PLUGIN_HEADER

View File

@@ -0,0 +1,46 @@
/*
* 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.
*/
#import <CoreGraphics/CoreGraphics.h>
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <React/RCTDefines.h>
RCT_EXTERN NSString *const EXTRAPOLATE_TYPE_IDENTITY;
RCT_EXTERN NSString *const EXTRAPOLATE_TYPE_CLAMP;
RCT_EXTERN NSString *const EXTRAPOLATE_TYPE_EXTEND;
RCT_EXTERN NSUInteger RCTFindIndexOfNearestValue(CGFloat value, NSArray<NSNumber *> *range);
RCT_EXTERN CGFloat RCTInterpolateValue(
CGFloat value,
CGFloat inputMin,
CGFloat inputMax,
CGFloat outputMin,
CGFloat outputMax,
NSString *extrapolateLeft,
NSString *extrapolateRight);
RCT_EXTERN CGFloat RCTInterpolateValueInRange(
CGFloat value,
NSArray<NSNumber *> *inputRange,
NSArray<NSNumber *> *outputRange,
NSString *extrapolateLeft,
NSString *extrapolateRight);
RCT_EXTERN uint32_t
RCTInterpolateColorInRange(CGFloat value, NSArray<NSNumber *> *inputRange, NSArray<UIColor *> *outputRange);
// Represents a color as a int32_t. RGB components are assumed to be in [0-255] range and alpha in [0-1] range
RCT_EXTERN uint32_t RCTColorFromComponents(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha);
/**
* Coefficient to slow down animations, respects the ios
* simulator `Slow Animations (⌘T)` option.
*/
RCT_EXTERN CGFloat RCTAnimationDragCoefficient(void);

View File

@@ -0,0 +1,135 @@
/*
* 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.
*/
#import <React/RCTAnimationUtils.h>
#import <React/RCTLog.h>
NSString *const EXTRAPOLATE_TYPE_IDENTITY = @"identity";
NSString *const EXTRAPOLATE_TYPE_CLAMP = @"clamp";
NSString *const EXTRAPOLATE_TYPE_EXTEND = @"extend";
NSUInteger RCTFindIndexOfNearestValue(CGFloat value, NSArray<NSNumber *> *range)
{
NSUInteger index;
NSUInteger rangeCount = range.count;
for (index = 1; index < rangeCount - 1; index++) {
NSNumber *inputValue = range[index];
if (inputValue.doubleValue >= value) {
break;
}
}
return index - 1;
}
/**
* Interpolates value by remapping it linearly fromMin->fromMax to toMin->toMax
*/
CGFloat RCTInterpolateValue(
CGFloat value,
CGFloat inputMin,
CGFloat inputMax,
CGFloat outputMin,
CGFloat outputMax,
NSString *extrapolateLeft,
NSString *extrapolateRight)
{
if (value < inputMin) {
if ([extrapolateLeft isEqualToString:EXTRAPOLATE_TYPE_IDENTITY]) {
return value;
} else if ([extrapolateLeft isEqualToString:EXTRAPOLATE_TYPE_CLAMP]) {
value = inputMin;
} else if ([extrapolateLeft isEqualToString:EXTRAPOLATE_TYPE_EXTEND]) {
// noop
} else {
RCTLogError(@"Invalid extrapolation type %@ for left extrapolation", extrapolateLeft);
}
}
if (value > inputMax) {
if ([extrapolateRight isEqualToString:EXTRAPOLATE_TYPE_IDENTITY]) {
return value;
} else if ([extrapolateRight isEqualToString:EXTRAPOLATE_TYPE_CLAMP]) {
value = inputMax;
} else if ([extrapolateRight isEqualToString:EXTRAPOLATE_TYPE_EXTEND]) {
// noop
} else {
RCTLogError(@"Invalid extrapolation type %@ for right extrapolation", extrapolateRight);
}
}
return outputMin + (value - inputMin) * (outputMax - outputMin) / (inputMax - inputMin);
}
/**
* Interpolates value by mapping it from the inputRange to the outputRange.
*/
CGFloat RCTInterpolateValueInRange(
CGFloat value,
NSArray<NSNumber *> *inputRange,
NSArray<NSNumber *> *outputRange,
NSString *extrapolateLeft,
NSString *extrapolateRight)
{
NSUInteger rangeIndex = RCTFindIndexOfNearestValue(value, inputRange);
CGFloat inputMin = inputRange[rangeIndex].doubleValue;
CGFloat inputMax = inputRange[rangeIndex + 1].doubleValue;
CGFloat outputMin = outputRange[rangeIndex].doubleValue;
CGFloat outputMax = outputRange[rangeIndex + 1].doubleValue;
return RCTInterpolateValue(value, inputMin, inputMax, outputMin, outputMax, extrapolateLeft, extrapolateRight);
}
uint32_t RCTInterpolateColorInRange(CGFloat value, NSArray<NSNumber *> *inputRange, NSArray<UIColor *> *outputRange)
{
NSUInteger rangeIndex = RCTFindIndexOfNearestValue(value, inputRange);
CGFloat inputMin = inputRange[rangeIndex].doubleValue;
CGFloat inputMax = inputRange[rangeIndex + 1].doubleValue;
CGFloat redMin;
CGFloat greenMin;
CGFloat blueMin;
CGFloat alphaMin;
[outputRange[rangeIndex] getRed:&redMin green:&greenMin blue:&blueMin alpha:&alphaMin];
CGFloat redMax;
CGFloat greenMax;
CGFloat blueMax;
CGFloat alphaMax;
[outputRange[rangeIndex + 1] getRed:&redMax green:&greenMax blue:&blueMax alpha:&alphaMax];
return RCTColorFromComponents(
0xFF * (redMin + (value - inputMin) * (redMax - redMin) / (inputMax - inputMin)),
0xFF * (greenMin + (value - inputMin) * (greenMax - greenMin) / (inputMax - inputMin)),
0xFF * (blueMin + (value - inputMin) * (blueMax - blueMin) / (inputMax - inputMin)),
alphaMin + (value - inputMin) * (alphaMax - alphaMin) / (inputMax - inputMin));
}
uint32_t RCTColorFromComponents(CGFloat red, CGFloat green, CGFloat blue, CGFloat alpha)
{
return ((uint32_t)round(alpha * 255) & 0xFF) << 24 | ((uint32_t)round(red) & 0xFF) << 16 |
((uint32_t)round(green) & 0xFF) << 8 | ((uint32_t)round(blue) & 0xFF);
}
#if TARGET_IPHONE_SIMULATOR
// Based on https://stackoverflow.com/a/13307674
UIKIT_EXTERN float UIAnimationDragCoefficient(void);
#endif
CGFloat RCTAnimationDragCoefficient(void)
{
#if TARGET_IPHONE_SIMULATOR
if (NSClassFromString(@"XCTest") != nil) {
// UIAnimationDragCoefficient is 10.0 in tests for some reason, but
// we need it to be 1.0. Fixes T34233294
return 1.0;
} else {
return (CGFloat)UIAnimationDragCoefficient();
}
#else
return 1.0;
#endif
}

View File

@@ -0,0 +1,28 @@
/*
* 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.
*/
#import <React/RCTBridgeModule.h>
#import <React/RCTEventDispatcherProtocol.h>
#import <React/RCTEventEmitter.h>
#import <React/RCTSurfacePresenterStub.h>
#import <React/RCTUIManager.h>
#import <React/RCTUIManagerObserverCoordinator.h>
#import <React/RCTUIManagerUtils.h>
#import "RCTValueAnimatedNode.h"
// TODO T69437152 @petetheheat - Delete this fork when Fabric ships to 100%.
// NOTE: This module is temporarily forked (see RCTNativeAnimatedTurboModule).
// When making any changes, be sure to apply them to the fork as well.
@interface RCTNativeAnimatedModule : RCTEventEmitter <
RCTBridgeModule,
RCTValueAnimatedNodeObserver,
RCTEventDispatcherObserver,
RCTUIManagerObserver,
RCTSurfacePresenterObserver>
@end

View File

@@ -0,0 +1,391 @@
/*
* 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.
*/
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <React/RCTInitializing.h>
#import <React/RCTLog.h>
#import <React/RCTNativeAnimatedModule.h>
#import <React/RCTNativeAnimatedNodesManager.h>
#import <RCTTypeSafety/RCTConvertHelpers.h>
#import "RCTAnimationPlugins.h"
typedef void (^AnimatedOperation)(RCTNativeAnimatedNodesManager *nodesManager);
@interface RCTNativeAnimatedModule () <RCTInitializing>
@end
@implementation RCTNativeAnimatedModule {
RCTNativeAnimatedNodesManager *_nodesManager;
// Operations called after views have been updated.
NSMutableArray<AnimatedOperation> *_operations;
// Operations called before views have been updated.
NSMutableArray<AnimatedOperation> *_preOperations;
NSMutableDictionary<NSNumber *, NSNumber *> *_animIdIsManagedByFabric;
}
RCT_EXPORT_MODULE();
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
- (instancetype)init
{
if (self = [super init]) {
_operations = [NSMutableArray new];
_preOperations = [NSMutableArray new];
_animIdIsManagedByFabric = [NSMutableDictionary new];
}
return self;
}
- (void)invalidate
{
[super invalidate];
[_nodesManager stopAnimationLoop];
[[self.moduleRegistry moduleForName:"EventDispatcher"] removeDispatchObserver:self];
[self.bridge.uiManager.observerCoordinator removeObserver:self];
[self.bridge.surfacePresenter removeObserver:self];
}
- (dispatch_queue_t)methodQueue
{
// This module needs to be on the same queue as the UIManager to avoid
// having to lock `_operations` and `_preOperations` since `uiManagerWillPerformMounting`
// will be called from that queue.
return RCTGetUIManagerQueue();
}
- (void)setBridge:(RCTBridge *)bridge
{
[super setBridge:bridge];
_nodesManager = [[RCTNativeAnimatedNodesManager alloc] initWithBridge:self.bridge
surfacePresenter:bridge.surfacePresenter];
[bridge.uiManager.observerCoordinator addObserver:self];
[bridge.surfacePresenter addObserver:self];
}
- (void)initialize
{
[[self.moduleRegistry moduleForName:"EventDispatcher"] addDispatchObserver:self];
}
/*
* This selector should only be invoked in bridgeless mode, which is not compatible with this non turbo module.
*/
- (void)setSurfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter
{
RCTLogWarn(@"setSurfacePresenter should only be invoked in RCTNativeAnimatedTurboModule");
}
#pragma mark-- API
RCT_EXPORT_METHOD(createAnimatedNode : (double)tag config : (NSDictionary<NSString *, id> *)config)
{
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager createAnimatedNode:[NSNumber numberWithDouble:tag] config:config];
}];
}
RCT_EXPORT_METHOD(updateAnimatedNodeConfig : (double)tag config : (NSDictionary<NSString *, id> *)config)
{
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager updateAnimatedNodeConfig:[NSNumber numberWithDouble:tag] config:config];
}];
}
RCT_EXPORT_METHOD(connectAnimatedNodes : (double)parentTag childTag : (double)childTag)
{
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager connectAnimatedNodes:[NSNumber numberWithDouble:parentTag]
childTag:[NSNumber numberWithDouble:childTag]];
}];
}
RCT_EXPORT_METHOD(disconnectAnimatedNodes : (double)parentTag childTag : (double)childTag)
{
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager disconnectAnimatedNodes:[NSNumber numberWithDouble:parentTag]
childTag:[NSNumber numberWithDouble:childTag]];
}];
}
RCT_EXPORT_METHOD(
startAnimatingNode : (double)animationId nodeTag : (double)nodeTag config : (NSDictionary<NSString *, id> *)
config endCallback : (RCTResponseSenderBlock)callBack)
{
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager startAnimatingNode:[NSNumber numberWithDouble:animationId]
nodeTag:[NSNumber numberWithDouble:nodeTag]
config:config
endCallback:callBack];
}];
RCTExecuteOnMainQueue(^{
if (![self->_nodesManager isNodeManagedByFabric:[NSNumber numberWithDouble:nodeTag]]) {
return;
}
RCTExecuteOnUIManagerQueue(^{
self->_animIdIsManagedByFabric[[NSNumber numberWithDouble:animationId]] = @YES;
[self flushOperationQueues];
});
});
}
RCT_EXPORT_METHOD(stopAnimation : (double)animationId)
{
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager stopAnimation:[NSNumber numberWithDouble:animationId]];
}];
if ([_animIdIsManagedByFabric[[NSNumber numberWithDouble:animationId]] boolValue]) {
[self flushOperationQueues];
}
}
RCT_EXPORT_METHOD(setAnimatedNodeValue : (double)nodeTag value : (double)value)
{
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager setAnimatedNodeValue:[NSNumber numberWithDouble:nodeTag] value:[NSNumber numberWithDouble:value]];
}];
}
RCT_EXPORT_METHOD(setAnimatedNodeOffset : (double)nodeTag offset : (double)offset)
{
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager setAnimatedNodeOffset:[NSNumber numberWithDouble:nodeTag] offset:[NSNumber numberWithDouble:offset]];
}];
}
RCT_EXPORT_METHOD(flattenAnimatedNodeOffset : (double)nodeTag)
{
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager flattenAnimatedNodeOffset:[NSNumber numberWithDouble:nodeTag]];
}];
}
RCT_EXPORT_METHOD(extractAnimatedNodeOffset : (double)nodeTag)
{
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager extractAnimatedNodeOffset:[NSNumber numberWithDouble:nodeTag]];
}];
}
RCT_EXPORT_METHOD(connectAnimatedNodeToView : (double)nodeTag viewTag : (double)viewTag)
{
NSString *viewName = [self.bridge.uiManager viewNameForReactTag:[NSNumber numberWithDouble:viewTag]];
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager connectAnimatedNodeToView:[NSNumber numberWithDouble:nodeTag]
viewTag:[NSNumber numberWithDouble:viewTag]
viewName:viewName];
}];
}
RCT_EXPORT_METHOD(disconnectAnimatedNodeFromView : (double)nodeTag viewTag : (double)viewTag)
{
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager disconnectAnimatedNodeFromView:[NSNumber numberWithDouble:nodeTag]
viewTag:[NSNumber numberWithDouble:viewTag]];
}];
}
RCT_EXPORT_METHOD(restoreDefaultValues : (double)nodeTag)
{
[self addPreOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager restoreDefaultValues:[NSNumber numberWithDouble:nodeTag]];
}];
}
RCT_EXPORT_METHOD(dropAnimatedNode : (double)tag)
{
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager dropAnimatedNode:[NSNumber numberWithDouble:tag]];
}];
}
RCT_EXPORT_METHOD(startListeningToAnimatedNodeValue : (double)tag)
{
__weak id<RCTValueAnimatedNodeObserver> valueObserver = self;
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager startListeningToAnimatedNodeValue:[NSNumber numberWithDouble:tag] valueObserver:valueObserver];
}];
}
RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue : (double)tag)
{
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager stopListeningToAnimatedNodeValue:[NSNumber numberWithDouble:tag]];
}];
}
RCT_EXPORT_METHOD(
addAnimatedEventToView : (double)viewTag eventName : (nonnull NSString *)
eventName eventMapping : (JS::NativeAnimatedModule::EventMapping &)eventMapping)
{
NSMutableDictionary *eventMappingDict = [NSMutableDictionary new];
eventMappingDict[@"nativeEventPath"] = RCTConvertVecToArray(eventMapping.nativeEventPath());
if (eventMapping.animatedValueTag()) {
eventMappingDict[@"animatedValueTag"] = @(*eventMapping.animatedValueTag());
}
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager addAnimatedEventToView:[NSNumber numberWithDouble:viewTag]
eventName:eventName
eventMapping:eventMappingDict];
}];
}
RCT_EXPORT_METHOD(
removeAnimatedEventFromView : (double)viewTag eventName : (nonnull NSString *)eventName animatedNodeTag : (double)
animatedNodeTag)
{
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager removeAnimatedEventFromView:[NSNumber numberWithDouble:viewTag]
eventName:eventName
animatedNodeTag:[NSNumber numberWithDouble:animatedNodeTag]];
}];
}
RCT_EXPORT_METHOD(getValue : (double)nodeTag saveValueCallback : (RCTResponseSenderBlock)saveValueCallback)
{
[self addOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager getValue:[NSNumber numberWithDouble:nodeTag] saveCallback:saveValueCallback];
}];
}
RCT_EXPORT_METHOD(queueAndExecuteBatchedOperations : (NSArray *)operationsAndArgs)
{
// TODO: implement in the future if we want the same optimization here as on Android
}
#pragma mark-- Batch handling
- (void)addOperationBlock:(AnimatedOperation)operation
{
[_operations addObject:operation];
}
- (void)addPreOperationBlock:(AnimatedOperation)operation
{
[_preOperations addObject:operation];
}
- (void)flushOperationQueues
{
if (_preOperations.count == 0 && _operations.count == 0) {
return;
}
NSArray<AnimatedOperation> *preOperations = _preOperations;
NSArray<AnimatedOperation> *operations = _operations;
_preOperations = [NSMutableArray new];
_operations = [NSMutableArray new];
RCTExecuteOnMainQueue(^{
for (AnimatedOperation operation in preOperations) {
operation(self->_nodesManager);
}
for (AnimatedOperation operation in operations) {
operation(self->_nodesManager);
}
[self->_nodesManager updateAnimations];
});
}
#pragma mark - RCTSurfacePresenterObserver
- (void)willMountComponentsWithRootTag:(NSInteger)rootTag
{
RCTAssertMainQueue();
RCTExecuteOnUIManagerQueue(^{
NSArray<AnimatedOperation> *preOperations = self->_preOperations;
self->_preOperations = [NSMutableArray new];
RCTExecuteOnMainQueue(^{
for (AnimatedOperation preOperation in preOperations) {
preOperation(self->_nodesManager);
}
});
});
}
- (void)didMountComponentsWithRootTag:(NSInteger)rootTag
{
RCTAssertMainQueue();
RCTExecuteOnUIManagerQueue(^{
NSArray<AnimatedOperation> *operations = self->_operations;
self->_operations = [NSMutableArray new];
RCTExecuteOnMainQueue(^{
for (AnimatedOperation operation in operations) {
operation(self->_nodesManager);
}
});
});
}
#pragma mark - RCTUIManagerObserver
- (void)uiManagerWillPerformMounting:(RCTUIManager *)uiManager
{
if (_preOperations.count == 0 && _operations.count == 0) {
return;
}
NSArray<AnimatedOperation> *preOperations = _preOperations;
NSArray<AnimatedOperation> *operations = _operations;
_preOperations = [NSMutableArray new];
_operations = [NSMutableArray new];
[uiManager
prependUIBlock:^(__unused RCTUIManager *manager, __unused NSDictionary<NSNumber *, UIView *> *viewRegistry) {
for (AnimatedOperation operation in preOperations) {
operation(self->_nodesManager);
}
}];
[uiManager addUIBlock:^(__unused RCTUIManager *manager, __unused NSDictionary<NSNumber *, UIView *> *viewRegistry) {
for (AnimatedOperation operation in operations) {
operation(self->_nodesManager);
}
[self->_nodesManager updateAnimations];
}];
}
#pragma mark-- Events
- (NSArray<NSString *> *)supportedEvents
{
// We need to declare the `onUserDrivenAnimationEnded` for compatibility with the New Architecture
// even if it will never be fired in the Old Architecture.
return @[ @"onAnimatedValueUpdate", @"onUserDrivenAnimationEnded" ];
}
- (void)animatedNode:(RCTValueAnimatedNode *)node didUpdateValue:(CGFloat)value
{
[self sendEventWithName:@"onAnimatedValueUpdate" body:@{@"tag" : node.nodeTag, @"value" : @(value)}];
}
- (void)eventDispatcherWillDispatchEvent:(id<RCTEvent>)event
{
// Events can be dispatched from any queue so we have to make sure handleAnimatedEvent
// is run from the main queue.
RCTExecuteOnMainQueue(^{
[self->_nodesManager handleAnimatedEvent:event];
});
}
@end
Class RCTNativeAnimatedModuleCls(void)
{
return RCTNativeAnimatedModule.class;
}

View File

@@ -0,0 +1,94 @@
/*
* 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.
*/
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTEventDispatcherProtocol.h>
#import <React/RCTSurfacePresenterStub.h>
#import <React/RCTUIManager.h>
@protocol RCTValueAnimatedNodeObserver;
NS_ASSUME_NONNULL_BEGIN
@interface RCTNativeAnimatedNodesManager : NSObject
- (nonnull instancetype)initWithBridge:(nullable RCTBridge *)bridge
surfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter;
- (void)updateAnimations;
- (void)stepAnimations:(CADisplayLink *)displaylink;
- (BOOL)isNodeManagedByFabric:(NSNumber *)tag;
- (void)getValue:(NSNumber *)nodeTag saveCallback:(RCTResponseSenderBlock)saveCallback;
// graph
- (void)createAnimatedNode:(NSNumber *)tag config:(NSDictionary<NSString *, id> *)config;
- (void)connectAnimatedNodes:(NSNumber *)parentTag childTag:(NSNumber *)childTag;
- (void)disconnectAnimatedNodes:(NSNumber *)parentTag childTag:(NSNumber *)childTag;
- (void)connectAnimatedNodeToView:(NSNumber *)nodeTag
viewTag:(NSNumber *)viewTag
viewName:(nullable NSString *)viewName;
- (void)restoreDefaultValues:(NSNumber *)nodeTag;
- (void)disconnectAnimatedNodeFromView:(NSNumber *)nodeTag viewTag:(NSNumber *)viewTag;
- (void)dropAnimatedNode:(NSNumber *)tag;
// mutations
- (void)setAnimatedNodeValue:(NSNumber *)nodeTag value:(NSNumber *)value;
- (void)setAnimatedNodeOffset:(NSNumber *)nodeTag offset:(NSNumber *)offset;
- (void)flattenAnimatedNodeOffset:(NSNumber *)nodeTag;
- (void)extractAnimatedNodeOffset:(NSNumber *)nodeTag;
- (void)updateAnimatedNodeConfig:(NSNumber *)tag config:(NSDictionary<NSString *, id> *)config;
// drivers
- (void)startAnimatingNode:(NSNumber *)animationId
nodeTag:(NSNumber *)nodeTag
config:(NSDictionary<NSString *, id> *)config
endCallback:(nullable RCTResponseSenderBlock)callBack;
- (void)stopAnimation:(NSNumber *)animationId;
- (void)stopAnimationLoop;
// events
- (void)addAnimatedEventToView:(NSNumber *)viewTag
eventName:(NSString *)eventName
eventMapping:(NSDictionary<NSString *, id> *)eventMapping;
- (void)removeAnimatedEventFromView:(NSNumber *)viewTag
eventName:(NSString *)eventName
animatedNodeTag:(NSNumber *)animatedNodeTag;
- (void)handleAnimatedEvent:(id<RCTEvent>)event;
// listeners
- (void)startListeningToAnimatedNodeValue:(NSNumber *)tag valueObserver:(id<RCTValueAnimatedNodeObserver>)valueObserver;
- (void)stopListeningToAnimatedNodeValue:(NSNumber *)tag;
- (NSSet<NSNumber *> *)getTagsOfConnectedNodesFrom:(NSNumber *)tag andEvent:(NSString *)eventName;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,511 @@
/*
* 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.
*/
#import <React/RCTNativeAnimatedNodesManager.h>
#import <React/RCTConvert.h>
#import <React/RCTAdditionAnimatedNode.h>
#import <React/RCTAnimatedNode.h>
#import <React/RCTAnimationDriver.h>
#import <React/RCTColorAnimatedNode.h>
#import <React/RCTDecayAnimation.h>
#import <React/RCTDiffClampAnimatedNode.h>
#import <React/RCTDivisionAnimatedNode.h>
#import <React/RCTEventAnimation.h>
#import <React/RCTFrameAnimation.h>
#import <React/RCTInterpolationAnimatedNode.h>
#import <React/RCTModuloAnimatedNode.h>
#import <React/RCTMultiplicationAnimatedNode.h>
#import <React/RCTObjectAnimatedNode.h>
#import <React/RCTPropsAnimatedNode.h>
#import <React/RCTSpringAnimation.h>
#import <React/RCTStyleAnimatedNode.h>
#import <React/RCTSubtractionAnimatedNode.h>
#import <React/RCTTrackingAnimatedNode.h>
#import <React/RCTTransformAnimatedNode.h>
#import <React/RCTValueAnimatedNode.h>
// We do some normalizing of the event names in RCTEventDispatcher#RCTNormalizeInputEventName.
// To make things simpler just get rid of the parts we change in the event names we use here.
// This is a lot easier than trying to denormalize because there would be multiple possible
// denormalized forms for a single input.
static NSString *RCTNormalizeAnimatedEventName(NSString *eventName)
{
if ([eventName hasPrefix:@"on"]) {
return [eventName substringFromIndex:2];
}
if ([eventName hasPrefix:@"top"]) {
return [eventName substringFromIndex:3];
}
return eventName;
}
@implementation RCTNativeAnimatedNodesManager {
__weak RCTBridge *_bridge;
__weak id<RCTSurfacePresenterStub> _surfacePresenter;
NSMutableDictionary<NSNumber *, RCTAnimatedNode *> *_animationNodes;
// Mapping of a view tag and an event name to a list of event animation drivers. 99% of the time
// there will be only one driver per mapping so all code code should be optimized around that.
NSMutableDictionary<NSString *, NSMutableArray<RCTEventAnimation *> *> *_eventDrivers;
NSMutableSet<id<RCTAnimationDriver>> *_activeAnimations;
CADisplayLink *_displayLink;
}
- (instancetype)initWithBridge:(nullable RCTBridge *)bridge
surfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter
{
if ((self = [super init]) != nullptr) {
_bridge = bridge;
_surfacePresenter = surfacePresenter;
_animationNodes = [NSMutableDictionary new];
_eventDrivers = [NSMutableDictionary new];
_activeAnimations = [NSMutableSet new];
}
return self;
}
- (BOOL)isNodeManagedByFabric:(NSNumber *)tag
{
RCTAnimatedNode *node = _animationNodes[tag];
if (node != nullptr) {
return [node isManagedByFabric];
}
return false;
}
#pragma mark-- Graph
- (void)createAnimatedNode:(NSNumber *)tag config:(NSDictionary<NSString *, id> *)config
{
static NSDictionary *map;
static dispatch_once_t mapToken;
dispatch_once(&mapToken, ^{
map = @{
@"style" : [RCTStyleAnimatedNode class],
@"value" : [RCTValueAnimatedNode class],
@"color" : [RCTColorAnimatedNode class],
@"props" : [RCTPropsAnimatedNode class],
@"interpolation" : [RCTInterpolationAnimatedNode class],
@"addition" : [RCTAdditionAnimatedNode class],
@"diffclamp" : [RCTDiffClampAnimatedNode class],
@"division" : [RCTDivisionAnimatedNode class],
@"multiplication" : [RCTMultiplicationAnimatedNode class],
@"modulus" : [RCTModuloAnimatedNode class],
@"subtraction" : [RCTSubtractionAnimatedNode class],
@"transform" : [RCTTransformAnimatedNode class],
@"tracking" : [RCTTrackingAnimatedNode class],
@"object" : [RCTObjectAnimatedNode class],
};
});
NSString *nodeType = [RCTConvert NSString:config[@"type"]];
Class nodeClass = map[nodeType];
if (nodeClass == nullptr) {
RCTLogError(@"Animated node type %@ not supported natively", nodeType);
return;
}
RCTAnimatedNode *node = [[nodeClass alloc] initWithTag:tag config:config];
node.manager = self;
_animationNodes[tag] = node;
[node setNeedsUpdate];
}
- (void)connectAnimatedNodes:(NSNumber *)parentTag childTag:(NSNumber *)childTag
{
RCTAssertParam(parentTag);
RCTAssertParam(childTag);
RCTAnimatedNode *parentNode = _animationNodes[parentTag];
RCTAnimatedNode *childNode = _animationNodes[childTag];
RCTAssertParam(parentNode);
RCTAssertParam(childNode);
[parentNode addChild:childNode];
[childNode setNeedsUpdate];
}
- (void)disconnectAnimatedNodes:(NSNumber *)parentTag childTag:(NSNumber *)childTag
{
RCTAssertParam(parentTag);
RCTAssertParam(childTag);
RCTAnimatedNode *parentNode = _animationNodes[parentTag];
RCTAnimatedNode *childNode = _animationNodes[childTag];
RCTAssertParam(parentNode);
RCTAssertParam(childNode);
[parentNode removeChild:childNode];
[childNode setNeedsUpdate];
}
- (void)connectAnimatedNodeToView:(NSNumber *)nodeTag viewTag:(NSNumber *)viewTag viewName:(nullable NSString *)viewName
{
RCTAnimatedNode *node = _animationNodes[nodeTag];
if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) {
// viewName is not used when node is managed by Fabric
[(RCTPropsAnimatedNode *)node connectToView:viewTag
viewName:viewName
bridge:_bridge
surfacePresenter:_surfacePresenter];
}
[node setNeedsUpdate];
}
- (void)disconnectAnimatedNodeFromView:(NSNumber *)nodeTag viewTag:(NSNumber *)viewTag
{
RCTAnimatedNode *node = _animationNodes[nodeTag];
if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) {
[(RCTPropsAnimatedNode *)node disconnectFromView:viewTag];
}
}
- (void)restoreDefaultValues:(NSNumber *)nodeTag
{
RCTAnimatedNode *node = _animationNodes[nodeTag];
// Restoring default values needs to happen before UIManager operations so it is
// possible the node hasn't been created yet if it is being connected and
// disconnected in the same batch. In that case we don't need to restore
// default values since it will never actually update the view.
if (node == nil) {
return;
}
if (![node isKindOfClass:[RCTPropsAnimatedNode class]]) {
RCTLogError(@"Not a props node.");
}
[(RCTPropsAnimatedNode *)node restoreDefaultValues];
}
- (void)dropAnimatedNode:(NSNumber *)tag
{
RCTAnimatedNode *node = _animationNodes[tag];
if (node != nullptr) {
[node detachNode];
[_animationNodes removeObjectForKey:tag];
}
}
#pragma mark-- Mutations
- (void)setAnimatedNodeValue:(NSNumber *)nodeTag value:(NSNumber *)value
{
RCTAnimatedNode *node = _animationNodes[nodeTag];
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
RCTLogError(@"Not a value node.");
return;
}
[self stopAnimationsForNode:node];
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
valueNode.value = value.floatValue;
[valueNode setNeedsUpdate];
}
- (void)setAnimatedNodeOffset:(NSNumber *)nodeTag offset:(NSNumber *)offset
{
RCTAnimatedNode *node = _animationNodes[nodeTag];
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
RCTLogError(@"Not a value node.");
return;
}
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
[valueNode setOffset:offset.floatValue];
[valueNode setNeedsUpdate];
}
- (void)flattenAnimatedNodeOffset:(NSNumber *)nodeTag
{
RCTAnimatedNode *node = _animationNodes[nodeTag];
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
RCTLogError(@"Not a value node.");
return;
}
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
[valueNode flattenOffset];
}
- (void)extractAnimatedNodeOffset:(NSNumber *)nodeTag
{
RCTAnimatedNode *node = _animationNodes[nodeTag];
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
RCTLogError(@"Not a value node.");
return;
}
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
[valueNode extractOffset];
}
- (void)getValue:(NSNumber *)nodeTag saveCallback:(RCTResponseSenderBlock)saveCallback
{
RCTAnimatedNode *node = _animationNodes[nodeTag];
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
RCTLogError(@"Not a value node.");
return;
}
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
;
saveCallback(@[ @(valueNode.value) ]);
}
- (void)updateAnimatedNodeConfig:(NSNumber *)tag config:(NSDictionary<NSString *, id> *)config
{
// TODO (T111179606): Support platform colors for color animations
}
#pragma mark-- Drivers
- (void)startAnimatingNode:(NSNumber *)animationId
nodeTag:(NSNumber *)nodeTag
config:(NSDictionary<NSString *, id> *)config
endCallback:(nullable RCTResponseSenderBlock)callBack
{
// check if the animation has already started
for (id<RCTAnimationDriver> driver in _activeAnimations) {
if ([driver.animationId isEqual:animationId]) {
// if the animation is running, we restart it with an updated configuration
[driver resetAnimationConfig:config];
return;
}
}
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)_animationNodes[nodeTag];
NSString *type = config[@"type"];
id<RCTAnimationDriver> animationDriver;
if ([type isEqual:@"frames"]) {
animationDriver = [[RCTFrameAnimation alloc] initWithId:animationId
config:config
forNode:valueNode
callBack:callBack];
} else if ([type isEqual:@"spring"]) {
animationDriver = [[RCTSpringAnimation alloc] initWithId:animationId
config:config
forNode:valueNode
callBack:callBack];
} else if ([type isEqual:@"decay"]) {
animationDriver = [[RCTDecayAnimation alloc] initWithId:animationId
config:config
forNode:valueNode
callBack:callBack];
} else {
RCTLogError(@"Unsupported animation type: %@", config[@"type"]);
return;
}
[_activeAnimations addObject:animationDriver];
[animationDriver startAnimation];
[self startAnimationLoopIfNeeded];
}
- (void)stopAnimation:(NSNumber *)animationId
{
for (id<RCTAnimationDriver> driver in _activeAnimations) {
if ([driver.animationId isEqual:animationId]) {
[driver stopAnimation];
[_activeAnimations removeObject:driver];
break;
}
}
}
- (void)stopAnimationsForNode:(RCTAnimatedNode *)node
{
NSMutableArray<id<RCTAnimationDriver>> *discarded = [NSMutableArray new];
for (id<RCTAnimationDriver> driver in _activeAnimations) {
if ([driver.valueNode isEqual:node]) {
[discarded addObject:driver];
}
}
for (id<RCTAnimationDriver> driver in discarded) {
[driver stopAnimation];
[_activeAnimations removeObject:driver];
}
}
#pragma mark-- Events
- (void)addAnimatedEventToView:(NSNumber *)viewTag
eventName:(NSString *)eventName
eventMapping:(NSDictionary<NSString *, id> *)eventMapping
{
NSNumber *nodeTag = [RCTConvert NSNumber:eventMapping[@"animatedValueTag"]];
RCTAnimatedNode *node = _animationNodes[nodeTag];
if (node == nullptr) {
RCTLogError(@"Animated node with tag %@ does not exist", nodeTag);
return;
}
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
RCTLogError(@"Animated node connected to event should be of type RCTValueAnimatedNode");
return;
}
NSArray<NSString *> *eventPath = [RCTConvert NSStringArray:eventMapping[@"nativeEventPath"]];
RCTEventAnimation *driver = [[RCTEventAnimation alloc] initWithEventPath:eventPath
valueNode:(RCTValueAnimatedNode *)node];
NSString *key = [NSString stringWithFormat:@"%@%@", viewTag, RCTNormalizeAnimatedEventName(eventName)];
if (_eventDrivers[key] != nil) {
[_eventDrivers[key] addObject:driver];
} else {
NSMutableArray<RCTEventAnimation *> *drivers = [NSMutableArray new];
[drivers addObject:driver];
_eventDrivers[key] = drivers;
}
// Handle onScrollEnded special events.
// These are triggered when the user stops dragging or when the
// scroll view stops decelerating after the user swiped
// The goal is to use this event to force a resync of the Shadow Tree
// with the Native tree
if ([eventName isEqualToString:@"onScroll"]) {
[self addAnimatedEventToView:viewTag eventName:@"onScrollEnded" eventMapping:eventMapping];
}
}
- (void)removeAnimatedEventFromView:(NSNumber *)viewTag
eventName:(NSString *)eventName
animatedNodeTag:(NSNumber *)animatedNodeTag
{
NSString *key = [NSString stringWithFormat:@"%@%@", viewTag, RCTNormalizeAnimatedEventName(eventName)];
if (_eventDrivers[key] != nil) {
if (_eventDrivers[key].count == 1) {
[_eventDrivers removeObjectForKey:key];
} else {
NSMutableArray<RCTEventAnimation *> *driversForKey = _eventDrivers[key];
for (NSUInteger i = 0; i < driversForKey.count; i++) {
if (driversForKey[i].valueNode.nodeTag == animatedNodeTag) {
[driversForKey removeObjectAtIndex:i];
break;
}
}
}
}
}
- (void)handleAnimatedEvent:(id<RCTEvent>)event
{
if (_eventDrivers.count == 0) {
return;
}
NSString *key = [NSString stringWithFormat:@"%@%@", event.viewTag, RCTNormalizeAnimatedEventName(event.eventName)];
NSMutableArray<RCTEventAnimation *> *driversForKey = _eventDrivers[key];
if (driversForKey != nullptr) {
for (RCTEventAnimation *driver in driversForKey) {
[self stopAnimationsForNode:driver.valueNode];
[driver updateWithEvent:event];
}
[self updateAnimations];
}
}
#pragma mark-- Listeners
- (void)startListeningToAnimatedNodeValue:(NSNumber *)tag valueObserver:(id<RCTValueAnimatedNodeObserver>)valueObserver
{
RCTAnimatedNode *node = _animationNodes[tag];
if ([node isKindOfClass:[RCTValueAnimatedNode class]]) {
((RCTValueAnimatedNode *)node).valueObserver = valueObserver;
}
}
- (void)stopListeningToAnimatedNodeValue:(NSNumber *)tag
{
RCTAnimatedNode *node = _animationNodes[tag];
if ([node isKindOfClass:[RCTValueAnimatedNode class]]) {
((RCTValueAnimatedNode *)node).valueObserver = nil;
}
}
#pragma mark-- Animation Loop
- (void)startAnimationLoopIfNeeded
{
if ((_displayLink == nullptr) && _activeAnimations.count > 0) {
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(stepAnimations:)];
[_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}
}
- (void)stopAnimationLoopIfNeeded
{
if (_activeAnimations.count == 0) {
[self stopAnimationLoop];
}
}
- (void)stopAnimationLoop
{
if (_displayLink != nullptr) {
[_displayLink invalidate];
_displayLink = nil;
}
}
- (void)stepAnimations:(CADisplayLink *)displaylink
{
NSTimeInterval time = displaylink.timestamp;
for (id<RCTAnimationDriver> animationDriver in _activeAnimations) {
[animationDriver stepAnimationWithTime:time];
}
[self updateAnimations];
for (id<RCTAnimationDriver> animationDriver in [_activeAnimations copy]) {
if (animationDriver.animationHasFinished) {
[animationDriver stopAnimation];
[_activeAnimations removeObject:animationDriver];
}
}
[self stopAnimationLoopIfNeeded];
}
- (NSSet<NSNumber *> *)getTagsOfConnectedNodesFrom:(NSNumber *)tag andEvent:(NSString *)eventName
{
NSMutableSet<NSNumber *> *tags = [NSMutableSet new];
NSString *key = [NSString stringWithFormat:@"%@%@", tag, RCTNormalizeAnimatedEventName(eventName)];
NSArray<RCTEventAnimation *> *eventAnimations = _eventDrivers[key];
for (RCTEventAnimation *animation in eventAnimations) {
NSNumber *nodeTag = [animation.valueNode nodeTag];
if (nodeTag != nullptr) {
[tags addObject:nodeTag];
}
for (NSNumber *childNodeKey in [animation.valueNode childNodes]) {
[tags addObject:childNodeKey];
}
}
return tags;
}
#pragma mark-- Updates
- (void)updateAnimations
{
[_animationNodes enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, RCTAnimatedNode *node, BOOL *stop) {
if (node.needsUpdate) {
[node updateNodeIfNecessary];
}
}];
}
@end

View File

@@ -0,0 +1,25 @@
/*
* 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.
*/
#import <React/RCTBridgeModule.h>
#import <React/RCTEventDispatcherProtocol.h>
#import <React/RCTEventEmitter.h>
#import <React/RCTSurfacePresenterStub.h>
#import <React/RCTUIManagerUtils.h>
#import "RCTValueAnimatedNode.h"
// TODO T69437152 @petetheheat - Delete this fork when Fabric ships to 100%.
// NOTE: This module is temporarily forked (see RCTNativeAnimatedModule).
// When making any changes, be sure to apply them to the fork as well.
@interface RCTNativeAnimatedTurboModule : RCTEventEmitter <
RCTBridgeModule,
RCTValueAnimatedNodeObserver,
RCTEventDispatcherObserver,
RCTSurfacePresenterObserver>
@end

View File

@@ -0,0 +1,381 @@
/*
* 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.
*/
#import <FBReactNativeSpec/FBReactNativeSpec.h>
#import <RCTTypeSafety/RCTConvertHelpers.h>
#import <React/RCTInitializing.h>
#import <React/RCTNativeAnimatedNodesManager.h>
#import <React/RCTNativeAnimatedTurboModule.h>
#import <react/featureflags/ReactNativeFeatureFlags.h>
#import "RCTAnimationPlugins.h"
typedef void (^AnimatedOperation)(RCTNativeAnimatedNodesManager *nodesManager);
@interface RCTNativeAnimatedTurboModule () <NativeAnimatedModuleSpec, RCTInitializing>
@end
@implementation RCTNativeAnimatedTurboModule {
RCTNativeAnimatedNodesManager *_nodesManager;
__weak id<RCTSurfacePresenterStub> _surfacePresenter;
// Operations called after views have been updated.
NSMutableArray<AnimatedOperation> *_operations;
// Operations called before views have been updated.
NSMutableArray<AnimatedOperation> *_preOperations;
NSSet<NSString *> *_userDrivenAnimationEndedEvents;
}
RCT_EXPORT_MODULE();
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
- (instancetype)init
{
if (self = [super init]) {
_operations = [NSMutableArray new];
_preOperations = [NSMutableArray new];
_userDrivenAnimationEndedEvents = [NSSet setWithArray:@[ @"onScrollEnded" ]];
}
return self;
}
- (void)initialize
{
// _surfacePresenter set in setSurfacePresenter:
_nodesManager = [[RCTNativeAnimatedNodesManager alloc] initWithBridge:nil surfacePresenter:_surfacePresenter];
[_surfacePresenter addObserver:self];
[[self.moduleRegistry moduleForName:"EventDispatcher"] addDispatchObserver:self];
}
- (void)invalidate
{
[super invalidate];
[_nodesManager stopAnimationLoop];
[[self.moduleRegistry moduleForName:"EventDispatcher"] removeDispatchObserver:self];
[_surfacePresenter removeObserver:self];
}
/*
* In bridgeless mode, `setBridge` is never called during initializtion. Instead this selector is invoked via
* BridgelessTurboModuleSetup.
*/
- (void)setSurfacePresenter:(id<RCTSurfacePresenterStub>)surfacePresenter
{
_surfacePresenter = surfacePresenter;
}
#pragma mark-- API
RCT_EXPORT_METHOD(startOperationBatch) {}
RCT_EXPORT_METHOD(finishOperationBatch) {}
RCT_EXPORT_METHOD(createAnimatedNode : (double)tag config : (NSDictionary<NSString *, id> *)config)
{
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager createAnimatedNode:[NSNumber numberWithDouble:tag] config:config];
}];
}
RCT_EXPORT_METHOD(updateAnimatedNodeConfig : (double)tag config : (NSDictionary<NSString *, id> *)config)
{
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager updateAnimatedNodeConfig:[NSNumber numberWithDouble:tag] config:config];
}];
}
RCT_EXPORT_METHOD(connectAnimatedNodes : (double)parentTag childTag : (double)childTag)
{
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager connectAnimatedNodes:[NSNumber numberWithDouble:parentTag]
childTag:[NSNumber numberWithDouble:childTag]];
}];
}
RCT_EXPORT_METHOD(disconnectAnimatedNodes : (double)parentTag childTag : (double)childTag)
{
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager disconnectAnimatedNodes:[NSNumber numberWithDouble:parentTag]
childTag:[NSNumber numberWithDouble:childTag]];
}];
}
RCT_EXPORT_METHOD(
startAnimatingNode : (double)animationId nodeTag : (double)nodeTag config : (NSDictionary<NSString *, id> *)
config endCallback : (RCTResponseSenderBlock)callBack)
{
[self queueFlushedOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager startAnimatingNode:[NSNumber numberWithDouble:animationId]
nodeTag:[NSNumber numberWithDouble:nodeTag]
config:config
endCallback:callBack];
}];
}
RCT_EXPORT_METHOD(stopAnimation : (double)animationId)
{
[self queueFlushedOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager stopAnimation:[NSNumber numberWithDouble:animationId]];
}];
}
RCT_EXPORT_METHOD(setAnimatedNodeValue : (double)nodeTag value : (double)value)
{
[self queueFlushedOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager setAnimatedNodeValue:[NSNumber numberWithDouble:nodeTag] value:[NSNumber numberWithDouble:value]];
}];
}
RCT_EXPORT_METHOD(setAnimatedNodeOffset : (double)nodeTag offset : (double)offset)
{
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager setAnimatedNodeOffset:[NSNumber numberWithDouble:nodeTag] offset:[NSNumber numberWithDouble:offset]];
}];
}
RCT_EXPORT_METHOD(flattenAnimatedNodeOffset : (double)nodeTag)
{
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager flattenAnimatedNodeOffset:[NSNumber numberWithDouble:nodeTag]];
}];
}
RCT_EXPORT_METHOD(extractAnimatedNodeOffset : (double)nodeTag)
{
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager extractAnimatedNodeOffset:[NSNumber numberWithDouble:nodeTag]];
}];
}
RCT_EXPORT_METHOD(connectAnimatedNodeToView : (double)nodeTag viewTag : (double)viewTag)
{
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
// viewName is not used when node is managed by Fabric, and nodes are always managed by Fabric in Bridgeless.
[nodesManager connectAnimatedNodeToView:[NSNumber numberWithDouble:nodeTag]
viewTag:[NSNumber numberWithDouble:viewTag]
viewName:nil];
}];
}
RCT_EXPORT_METHOD(disconnectAnimatedNodeFromView : (double)nodeTag viewTag : (double)viewTag)
{
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager disconnectAnimatedNodeFromView:[NSNumber numberWithDouble:nodeTag]
viewTag:[NSNumber numberWithDouble:viewTag]];
}];
}
RCT_EXPORT_METHOD(restoreDefaultValues : (double)nodeTag)
{
[self queuePreOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager restoreDefaultValues:[NSNumber numberWithDouble:nodeTag]];
}];
}
RCT_EXPORT_METHOD(dropAnimatedNode : (double)tag)
{
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager dropAnimatedNode:[NSNumber numberWithDouble:tag]];
}];
}
RCT_EXPORT_METHOD(startListeningToAnimatedNodeValue : (double)tag)
{
__weak id<RCTValueAnimatedNodeObserver> valueObserver = self;
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager startListeningToAnimatedNodeValue:[NSNumber numberWithDouble:tag] valueObserver:valueObserver];
}];
}
RCT_EXPORT_METHOD(stopListeningToAnimatedNodeValue : (double)tag)
{
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager stopListeningToAnimatedNodeValue:[NSNumber numberWithDouble:tag]];
}];
}
RCT_EXPORT_METHOD(
addAnimatedEventToView : (double)viewTag eventName : (nonnull NSString *)
eventName eventMapping : (JS::NativeAnimatedModule::EventMapping &)eventMapping)
{
NSMutableDictionary *eventMappingDict = [NSMutableDictionary new];
eventMappingDict[@"nativeEventPath"] = RCTConvertVecToArray(eventMapping.nativeEventPath());
if (eventMapping.animatedValueTag()) {
eventMappingDict[@"animatedValueTag"] = @(*eventMapping.animatedValueTag());
}
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager addAnimatedEventToView:[NSNumber numberWithDouble:viewTag]
eventName:eventName
eventMapping:eventMappingDict];
}];
}
RCT_EXPORT_METHOD(
removeAnimatedEventFromView : (double)viewTag eventName : (nonnull NSString *)eventName animatedNodeTag : (double)
animatedNodeTag)
{
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager removeAnimatedEventFromView:[NSNumber numberWithDouble:viewTag]
eventName:eventName
animatedNodeTag:[NSNumber numberWithDouble:animatedNodeTag]];
}];
}
RCT_EXPORT_METHOD(getValue : (double)nodeTag saveValueCallback : (RCTResponseSenderBlock)saveValueCallback)
{
[self queueOperationBlock:^(RCTNativeAnimatedNodesManager *nodesManager) {
[nodesManager getValue:[NSNumber numberWithDouble:nodeTag] saveCallback:saveValueCallback];
}];
}
RCT_EXPORT_METHOD(queueAndExecuteBatchedOperations : (NSArray *)operationsAndArgs)
{
// TODO: implement in the future if we want the same optimization here as on Android
}
#pragma mark-- Batch handling
- (void)queueFlushedOperationBlock:(AnimatedOperation)operation
{
dispatch_async(RCTGetUIManagerQueue(), ^{
[self addOperationBlock:operation];
// In Bridge, flushing of native animations is done from RCTCxxBridge batchDidComplete().
// Since RCTCxxBridge doesn't exist in Bridgeless, and components are not remounted in Fabric for native
// animations, flush here for changes in Animated.Value for Animated.event.
[self flushOperationQueues];
});
}
- (void)queueOperationBlock:(AnimatedOperation)operation
{
dispatch_async(RCTGetUIManagerQueue(), ^{
[self addOperationBlock:operation];
});
}
- (void)queuePreOperationBlock:(AnimatedOperation)operation
{
dispatch_async(RCTGetUIManagerQueue(), ^{
[self addPreOperationBlock:operation];
});
}
- (void)addOperationBlock:(AnimatedOperation)operation
{
[_operations addObject:operation];
}
- (void)addPreOperationBlock:(AnimatedOperation)operation
{
[_preOperations addObject:operation];
}
- (void)flushOperationQueues
{
if (_preOperations.count == 0 && _operations.count == 0) {
return;
}
NSArray<AnimatedOperation> *preOperations = _preOperations;
NSArray<AnimatedOperation> *operations = _operations;
_preOperations = [NSMutableArray new];
_operations = [NSMutableArray new];
RCTExecuteOnMainQueue(^{
for (AnimatedOperation operation in preOperations) {
operation(self->_nodesManager);
}
for (AnimatedOperation operation in operations) {
operation(self->_nodesManager);
}
[self->_nodesManager updateAnimations];
});
}
#pragma mark - RCTSurfacePresenterObserver
- (void)willMountComponentsWithRootTag:(NSInteger)rootTag
{
RCTAssertMainQueue();
RCTExecuteOnUIManagerQueue(^{
NSArray<AnimatedOperation> *preOperations = self->_preOperations;
self->_preOperations = [NSMutableArray new];
RCTExecuteOnMainQueue(^{
for (AnimatedOperation preOperation in preOperations) {
preOperation(self->_nodesManager);
}
});
});
}
- (void)didMountComponentsWithRootTag:(NSInteger)rootTag
{
RCTAssertMainQueue();
RCTExecuteOnUIManagerQueue(^{
NSArray<AnimatedOperation> *operations = self->_operations;
self->_operations = [NSMutableArray new];
RCTExecuteOnMainQueue(^{
for (AnimatedOperation operation in operations) {
operation(self->_nodesManager);
}
});
});
}
#pragma mark-- Events
- (NSArray<NSString *> *)supportedEvents
{
return @[ @"onAnimatedValueUpdate", @"onUserDrivenAnimationEnded" ];
}
- (void)animatedNode:(RCTValueAnimatedNode *)node didUpdateValue:(CGFloat)value
{
[self sendEventWithName:@"onAnimatedValueUpdate" body:@{@"tag" : node.nodeTag, @"value" : @(value)}];
}
- (void)userDrivenAnimationEnded:(NSArray<NSNumber *> *)nodes
{
[self sendEventWithName:@"onUserDrivenAnimationEnded" body:@{@"tags" : nodes}];
}
- (void)eventDispatcherWillDispatchEvent:(id<RCTEvent>)event
{
// Events can be dispatched from any queue so we have to make sure handleAnimatedEvent
// is run from the main queue.
RCTExecuteOnMainQueue(^{
[self->_nodesManager handleAnimatedEvent:event];
if ([self->_userDrivenAnimationEndedEvents containsObject:event.eventName]) {
NSSet<NSNumber *> *tags = [self->_nodesManager getTagsOfConnectedNodesFrom:event.viewTag
andEvent:event.eventName];
if (tags.count > 0) {
[self userDrivenAnimationEnded:[tags allObjects]];
}
}
});
}
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeAnimatedModuleSpecJSI>(params);
}
@end
Class RCTNativeAnimatedTurboModuleCls(void)
{
return RCTNativeAnimatedTurboModule.class;
}

View File

@@ -0,0 +1,54 @@
# 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.
require "json"
package = JSON.parse(File.read(File.join(__dir__, "..", "..", "package.json")))
version = package['version']
source = { :git => 'https://github.com/facebook/react-native.git' }
if version == '1000.0.0'
# This is an unpublished version, use the latest commit hash of the react-native repo, which were presumably in.
source[:commit] = `git rev-parse HEAD`.strip if system("git rev-parse --git-dir > /dev/null 2>&1")
else
source[:tag] = "v#{version}"
end
header_search_paths = [
"\"${PODS_ROOT}/Headers/Public/ReactCodegen/react/renderer/components\"",
]
Pod::Spec.new do |s|
s.name = "React-RCTAnimation"
s.version = version
s.summary = "A native driver for the Animated API."
s.homepage = "https://reactnative.dev/"
s.license = package["license"]
s.author = "Meta Platforms, Inc. and its affiliates"
s.platforms = min_supported_versions
s.compiler_flags = '-Wno-nullability-completeness'
s.source = source
s.source_files = podspec_sources("**/*.{h,m,mm}", "**/*.h")
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
s.header_dir = "RCTAnimation"
s.pod_target_xcconfig = {
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => rct_cxx_language_standard(),
"HEADER_SEARCH_PATHS" => header_search_paths.join(" ")
}
s.framework = ["UIKit", "QuartzCore"]
s.dependency "RCTTypeSafety"
s.dependency "React-jsi"
s.dependency "React-Core/RCTAnimationHeaders"
add_dependency(s, "React-RCTFBReactNativeSpec")
add_dependency(s, "ReactCommon", :subspec => "turbomodule/core", :additional_framework_paths => ["react/nativemodule/core"])
add_dependency(s, "React-NativeModulesApple")
add_dependency(s, "React-featureflags")
add_rn_third_party_dependencies(s)
add_rncore_dependency(s)
end