This commit is contained in:
Yamozha
2021-04-02 02:24:13 +03:00
parent c23950b545
commit 7256d79e2c
31493 changed files with 3036630 additions and 0 deletions

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) Facebook, Inc. and its 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) Facebook, Inc. and its 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,128 @@
/*
* Copyright (c) Facebook, Inc. and its 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 <UIKit/UIKit.h>
#import <React/RCTConvert.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
{
_valueNode = nil;
if (_callback) {
_callback(@[@{
@"finished": @(_animationHasFinished)
}]);
}
}
- (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,21 @@
/*
* Copyright (c) Facebook, Inc. and its 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/RCTEventDispatcher.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,38 @@
/*
* Copyright (c) Facebook, Inc. and its 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])) {
_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) Facebook, Inc. and its 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,158 @@
/*
* Copyright (c) Facebook, Inc. and its 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
{
_valueNode = nil;
if (_callback) {
_callback(@[@{
@"finished": @(_animationHasFinished)
}]);
}
}
- (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 = startIndex * RCTSingleFrameInterval;
NSTimeInterval toInterval = 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) Facebook, Inc. and its 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,214 @@
/*
* Copyright (c) Facebook, Inc. and its 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
{
_valueNode = nil;
if (_callback) {
_callback(@[@{
@"finished": @(_animationHasFinished)
}]);
}
}
- (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) Facebook, Inc. and its 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,26 @@
/*
* Copyright (c) Facebook, Inc. and its 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,51 @@
/*
* Copyright (c) Facebook, Inc. and its 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,128 @@
/*
* Copyright (c) Facebook, Inc. and its 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])) {
_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) {
_childNodes = [NSMapTable strongToWeakObjectsMapTable];
}
if (child) {
[_childNodes setObject:child forKey:child.nodeTag];
[child onAttachedToNode:self];
}
}
- (void)removeChild:(RCTAnimatedNode *)child
{
if (!_childNodes) {
return;
}
if (child) {
[_childNodes removeObjectForKey:child.nodeTag];
[child onDetachedFromNode:self];
}
}
- (void)onAttachedToNode:(RCTAnimatedNode *)parent
{
if (!_parentNodes) {
_parentNodes = [NSMapTable strongToWeakObjectsMapTable];
}
if (parent) {
[_parentNodes setObject:parent forKey:parent.nodeTag];
}
}
- (void)onDetachedFromNode:(RCTAnimatedNode *)parent
{
if (!_parentNodes) {
return;
}
if (parent) {
[_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,12 @@
/*
* Copyright (c) Facebook, Inc. and its 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,61 @@
/*
* Copyright (c) Facebook, Inc. and its 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
{
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) Facebook, Inc. and its 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,33 @@
/*
* Copyright (c) Facebook, Inc. and its 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,12 @@
/*
* Copyright (c) Facebook, Inc. and its 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 RCTInterpolationAnimatedNode : RCTValueAnimatedNode
@end

View File

@ -0,0 +1,171 @@
/*
* Copyright (c) Facebook, Inc. and its 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/RCTInterpolationAnimatedNode.h>
#import <React/RCTAnimationUtils.h>
static NSRegularExpression *regex;
@implementation RCTInterpolationAnimatedNode
{
__weak RCTValueAnimatedNode *_parentNode;
NSArray<NSNumber *> *_inputRange;
NSArray<NSNumber *> *_outputRange;
NSArray<NSArray<NSNumber *> *> *_outputs;
NSArray<NSString *> *_soutputRange;
NSString *_extrapolateLeft;
NSString *_extrapolateRight;
NSUInteger _numVals;
bool _hasStringOutput;
bool _shouldRound;
NSArray<NSTextCheckingResult*> *_matches;
}
- (instancetype)initWithTag:(NSNumber *)tag
config:(NSDictionary<NSString *, id> *)config
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *fpRegex = @"[+-]?(\\d+\\.?\\d*|\\.\\d+)([eE][+-]?\\d+)?";
regex = [NSRegularExpression regularExpressionWithPattern:fpRegex options:NSRegularExpressionCaseInsensitive error:nil];
});
if ((self = [super initWithTag:tag config:config])) {
_inputRange = [config[@"inputRange"] copy];
NSMutableArray *outputRange = [NSMutableArray array];
NSMutableArray *soutputRange = [NSMutableArray array];
NSMutableArray<NSMutableArray<NSNumber *> *> *_outputRanges = [NSMutableArray array];
_hasStringOutput = NO;
for (id value in config[@"outputRange"]) {
if ([value isKindOfClass:[NSNumber class]]) {
[outputRange addObject:value];
} else if ([value isKindOfClass:[NSString class]]) {
/**
* Supports string shapes by extracting numbers so new values can be computed,
* and recombines those values into new strings of the same shape. Supports
* things like:
*
* rgba(123, 42, 99, 0.36) // colors
* -45deg // values with units
*/
NSMutableArray *output = [NSMutableArray array];
[_outputRanges addObject:output];
[soutputRange addObject:value];
_matches = [regex matchesInString:value options:0 range:NSMakeRange(0, [value length])];
for (NSTextCheckingResult *match in _matches) {
NSString* strNumber = [value substringWithRange:match.range];
[output addObject:[NSNumber numberWithDouble:strNumber.doubleValue]];
}
_hasStringOutput = YES;
[outputRange addObject:[output objectAtIndex:0]];
}
}
if (_hasStringOutput) {
// ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)']
// ->
// [
// [0, 50],
// [100, 150],
// [200, 250],
// [0, 0.5],
// ]
_numVals = [_matches count];
NSString *value = [soutputRange objectAtIndex:0];
_shouldRound = [value containsString:@"rgb"];
_matches = [regex matchesInString:value options:0 range:NSMakeRange(0, [value length])];
NSMutableArray<NSMutableArray<NSNumber *> *> *outputs = [NSMutableArray arrayWithCapacity:_numVals];
NSUInteger size = [soutputRange count];
for (NSUInteger j = 0; j < _numVals; j++) {
NSMutableArray *output = [NSMutableArray arrayWithCapacity:size];
[outputs addObject:output];
for (int i = 0; i < size; i++) {
[output addObject:[[_outputRanges objectAtIndex:i] objectAtIndex:j]];
}
}
_outputs = [outputs copy];
}
_outputRange = [outputRange copy];
_soutputRange = [soutputRange copy];
_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) {
return;
}
CGFloat inputValue = _parentNode.value;
CGFloat interpolated = RCTInterpolateValueInRange(inputValue,
_inputRange,
_outputRange,
_extrapolateLeft,
_extrapolateRight);
self.value = interpolated;
if (_hasStringOutput) {
// 'rgba(0, 100, 200, 0)'
// ->
// 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...'
if (_numVals > 1) {
NSString *text = _soutputRange[0];
NSMutableString *formattedText = [NSMutableString stringWithString:text];
NSUInteger i = _numVals;
for (NSTextCheckingResult *match in [_matches reverseObjectEnumerator]) {
CGFloat val = RCTInterpolateValueInRange(inputValue,
_inputRange,
_outputs[--i],
_extrapolateLeft,
_extrapolateRight);
NSString *str;
if (_shouldRound) {
// rgba requires that the r,g,b are integers.... so we want to round them, but we *dont* want to
// round the opacity (4th column).
bool isAlpha = i == 3;
CGFloat rounded = isAlpha ? round(val * 1000) / 1000 : round(val);
str = isAlpha ? [NSString stringWithFormat:@"%1.3f", rounded] : [NSString stringWithFormat:@"%1.0f", rounded];
} else {
NSNumber *numberValue = [NSNumber numberWithDouble:val];
str = [numberValue stringValue];
}
[formattedText replaceCharactersInRange:[match range] withString:str];
}
self.animatedObject = formattedText;
} else {
self.animatedObject = [regex stringByReplacingMatchesInString:_soutputRange[0]
options:0
range:NSMakeRange(0, _soutputRange[0].length)
withTemplate:[NSString stringWithFormat:@"%1f", interpolated]];
}
}
}
@end

View File

@ -0,0 +1,12 @@
/*
* Copyright (c) Facebook, Inc. and its 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) Facebook, Inc. and its 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) Facebook, Inc. and its 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,27 @@
/*
* Copyright (c) Facebook, Inc. and its 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,23 @@
/*
* Copyright (c) Facebook, Inc. and its 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"
@class RCTBridge;
@class RCTViewPropertyMapper;
@interface RCTPropsAnimatedNode : RCTAnimatedNode
- (void)connectToView:(NSNumber *)viewTag
viewName:(NSString *)viewName
bridge:(RCTBridge *)bridge;
- (void)disconnectFromView:(NSNumber *)viewTag;
- (void)restoreDefaultValues;
@end

View File

@ -0,0 +1,133 @@
/*
* Copyright (c) Facebook, Inc. and its 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/RCTLog.h>
#import <React/RCTSurfacePresenterStub.h>
#import <React/RCTUIManager.h>
#import <React/RCTAnimationUtils.h>
#import <React/RCTStyleAnimatedNode.h>
#import <React/RCTValueAnimatedNode.h>
@implementation RCTPropsAnimatedNode
{
NSNumber *_connectedViewTag;
NSNumber *_rootTag;
NSString *_connectedViewName;
__weak RCTBridge *_bridge;
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
{
_bridge = bridge;
_connectedViewTag = viewTag;
_connectedViewName = viewName;
_managedByFabric = RCTUIManagerTypeForTagIsFabric(viewTag);
_rootTag = nil;
}
- (void)disconnectFromView:(NSNumber *)viewTag
{
_bridge = nil;
_connectedViewTag = nil;
_connectedViewName = nil;
_managedByFabric = NO;
_rootTag = nil;
}
- (void)updateView
{
if (_managedByFabric) {
[_bridge.surfacePresenter synchronouslyUpdateViewOnUIThread:_connectedViewTag
props:_propsDictionary];
} else {
[_bridge.uiManager synchronouslyUpdateViewOnUIThread:_connectedViewTag
viewName:_connectedViewName
props:_propsDictionary];
}
}
- (void)restoreDefaultValues
{
// 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]]) {
[self->_propsDictionary addEntriesFromDictionary:[(RCTStyleAnimatedNode *)parentNode propsDictionary]];
} else if ([parentNode isKindOfClass:[RCTValueAnimatedNode class]]) {
NSString *property = [self propertyNameForParentTag:parentTag];
id animatedObject = [(RCTValueAnimatedNode *)parentNode animatedObject];
if (animatedObject) {
self->_propsDictionary[property] = animatedObject;
} else {
CGFloat value = [(RCTValueAnimatedNode *)parentNode value];
self->_propsDictionary[property] = @(value);
}
}
}
if (_propsDictionary.count) {
[self updateView];
}
}
@end

View File

@ -0,0 +1,14 @@
/*
* Copyright (c) Facebook, Inc. and its 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,51 @@
/*
* Copyright (c) Facebook, Inc. and its 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/RCTStyleAnimatedNode.h>
#import <React/RCTAnimationUtils.h>
#import <React/RCTValueAnimatedNode.h>
#import <React/RCTTransformAnimatedNode.h>
@implementation RCTStyleAnimatedNode
{
NSMutableDictionary<NSString *, NSObject *> *_propsDictionary;
}
- (instancetype)initWithTag:(NSNumber *)tag
config:(NSDictionary<NSString *, id> *)config
{
if ((self = [super initWithTag:tag config:config])) {
_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) {
if ([node isKindOfClass:[RCTValueAnimatedNode class]]) {
RCTValueAnimatedNode *parentNode = (RCTValueAnimatedNode *)node;
[self->_propsDictionary setObject:@(parentNode.value) forKey:property];
} else if ([node isKindOfClass:[RCTTransformAnimatedNode class]]) {
RCTTransformAnimatedNode *parentNode = (RCTTransformAnimatedNode *)node;
[self->_propsDictionary addEntriesFromDictionary:parentNode.propsDictionary];
}
}
}];
}
@end

View File

@ -0,0 +1,13 @@
/*
* Copyright (c) Facebook, Inc. and its 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,27 @@
/*
* Copyright (c) Facebook, Inc. and its 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,13 @@
/*
* Copyright (c) Facebook, Inc. and its 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,52 @@
/*
* Copyright (c) Facebook, Inc. and its 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/RCTTrackingAnimatedNode.h>
#import <React/RCTValueAnimatedNode.h>
#import <React/RCTNativeAnimatedNodesManager.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])) {
_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) Facebook, Inc. and its 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,57 @@
/*
* Copyright (c) Facebook, Inc. and its 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])) {
_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) Facebook, Inc. and its 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) id animatedObject;
@property (nonatomic, weak) id<RCTValueAnimatedNodeObserver> valueObserver;
@end

View File

@ -0,0 +1,56 @@
/*
* Copyright (c) Facebook, Inc. and its 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;
}
- (void)setValue:(CGFloat)value
{
_value = value;
if (_valueObserver) {
[_valueObserver animatedNode:self didUpdateValue:_value];
}
}
@end

View File

@ -0,0 +1,40 @@
/**
* Copyright (c) Facebook, Inc. and its 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));
#ifdef __cplusplus
}
#endif
#pragma GCC diagnostic pop
#endif // RN_DISABLE_OSS_PLUGIN_HEADER

View File

@ -0,0 +1,32 @@
/**
* Copyright (c) Facebook, Inc. and its 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) {
static std::unordered_map<std::string, Class (*)(void)> sCoreModuleClassMap = {
{"NativeAnimatedModule", RCTNativeAnimatedModuleCls},
};
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,38 @@
/*
* Copyright (c) Facebook, Inc. and its 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/RCTDefines.h>
static NSString *const EXTRAPOLATE_TYPE_IDENTITY = @"identity";
static NSString *const EXTRAPOLATE_TYPE_CLAMP = @"clamp";
static NSString *const EXTRAPOLATE_TYPE_EXTEND = @"extend";
RCT_EXTERN CGFloat RCTInterpolateValueInRange(CGFloat value,
NSArray<NSNumber *> *inputRange,
NSArray<NSNumber *> *outputRange,
NSString *extrapolateLeft,
NSString *extrapolateRight);
RCT_EXTERN CGFloat RCTInterpolateValue(CGFloat value,
CGFloat inputMin,
CGFloat inputMax,
CGFloat outputMin,
CGFloat outputMax,
NSString *extrapolateLeft,
NSString *extrapolateRight);
RCT_EXTERN CGFloat RCTRadiansToDegrees(CGFloat radians);
RCT_EXTERN CGFloat RCTDegreesToRadians(CGFloat degrees);
/**
* Coefficient to slow down animations, respects the ios
* simulator `Slow Animations (⌘T)` option.
*/
RCT_EXTERN CGFloat RCTAnimationDragCoefficient(void);

View File

@ -0,0 +1,115 @@
/*
* Copyright (c) Facebook, Inc. and its 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>
static 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);
}
CGFloat RCTRadiansToDegrees(CGFloat radians)
{
return radians * 180.0 / M_PI;
}
CGFloat RCTDegreesToRadians(CGFloat degrees)
{
return degrees / 180.0 * M_PI;
}
#if TARGET_IPHONE_SIMULATOR
// Based on https://stackoverflow.com/a/13307674
float UIAnimationDragCoefficient(void);
#endif
CGFloat RCTAnimationDragCoefficient()
{
#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,20 @@
/*
* Copyright (c) Facebook, Inc. and its 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/RCTEventDispatcher.h>
#import <React/RCTEventEmitter.h>
#import <React/RCTSurfacePresenterStub.h>
#import <React/RCTUIManager.h>
#import <React/RCTUIManagerObserverCoordinator.h>
#import <React/RCTUIManagerUtils.h>
#import "RCTValueAnimatedNode.h"
@interface RCTNativeAnimatedModule : RCTEventEmitter <RCTBridgeModule, RCTValueAnimatedNodeObserver, RCTEventDispatcherObserver, RCTUIManagerObserver, RCTSurfacePresenterObserver>
@end

View File

@ -0,0 +1,350 @@
/*
* Copyright (c) Facebook, Inc. and its 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/RCTNativeAnimatedModule.h>
#import <React/RCTNativeAnimatedNodesManager.h>
#import <RCTTypeSafety/RCTConvertHelpers.h>
#import "RCTAnimationPlugins.h"
typedef void (^AnimatedOperation)(RCTNativeAnimatedNodesManager *nodesManager);
@interface RCTNativeAnimatedModule() <NativeAnimatedModuleSpec>
@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();
- (void)invalidate
{
[_nodesManager stopAnimationLoop];
[self.bridge.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];
_operations = [NSMutableArray new];
_preOperations = [NSMutableArray new];
_animIdIsManagedByFabric = [NSMutableDictionary new];
[bridge.eventDispatcher addDispatchObserver:self];
[bridge.uiManager.observerCoordinator addObserver:self];
[bridge.surfacePresenter addObserver:self];
}
#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(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]];
}];
}
#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
{
return @[@"onAnimatedValueUpdate"];
}
- (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];
});
}
- (std::shared_ptr<facebook::react::TurboModule>)
getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
nativeInvoker:(std::shared_ptr<facebook::react::CallInvoker>)nativeInvoker
perfLogger:(id<RCTTurboModulePerformanceLogger>)perfLogger
{
return std::make_shared<facebook::react::NativeAnimatedModuleSpecJSI>(self, jsInvoker, nativeInvoker, perfLogger);
}
@end
Class RCTNativeAnimatedModuleCls(void) {
return RCTNativeAnimatedModule.class;
}

View File

@ -0,0 +1,88 @@
/*
* Copyright (c) Facebook, Inc. and its 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/RCTUIManager.h>
@protocol RCTValueAnimatedNodeObserver;
@interface RCTNativeAnimatedNodesManager : NSObject
- (nonnull instancetype)initWithBridge:(nonnull RCTBridge *)bridge;
- (void)updateAnimations;
- (void)stepAnimations:(nonnull CADisplayLink *)displaylink;
- (BOOL)isNodeManagedByFabric:(nonnull NSNumber *)tag;
// graph
- (void)createAnimatedNode:(nonnull NSNumber *)tag
config:(NSDictionary<NSString *, id> *__nonnull)config;
- (void)connectAnimatedNodes:(nonnull NSNumber *)parentTag
childTag:(nonnull NSNumber *)childTag;
- (void)disconnectAnimatedNodes:(nonnull NSNumber *)parentTag
childTag:(nonnull NSNumber *)childTag;
- (void)connectAnimatedNodeToView:(nonnull NSNumber *)nodeTag
viewTag:(nonnull NSNumber *)viewTag
viewName:(nonnull NSString *)viewName;
- (void)restoreDefaultValues:(nonnull NSNumber *)nodeTag;
- (void)disconnectAnimatedNodeFromView:(nonnull NSNumber *)nodeTag
viewTag:(nonnull NSNumber *)viewTag;
- (void)dropAnimatedNode:(nonnull NSNumber *)tag;
// mutations
- (void)setAnimatedNodeValue:(nonnull NSNumber *)nodeTag
value:(nonnull NSNumber *)value;
- (void)setAnimatedNodeOffset:(nonnull NSNumber *)nodeTag
offset:(nonnull NSNumber *)offset;
- (void)flattenAnimatedNodeOffset:(nonnull NSNumber *)nodeTag;
- (void)extractAnimatedNodeOffset:(nonnull NSNumber *)nodeTag;
// drivers
- (void)startAnimatingNode:(nonnull NSNumber *)animationId
nodeTag:(nonnull NSNumber *)nodeTag
config:(NSDictionary<NSString *, id> *__nonnull)config
endCallback:(nullable RCTResponseSenderBlock)callBack;
- (void)stopAnimation:(nonnull NSNumber *)animationId;
- (void)stopAnimationLoop;
// events
- (void)addAnimatedEventToView:(nonnull NSNumber *)viewTag
eventName:(nonnull NSString *)eventName
eventMapping:(NSDictionary<NSString *, id> *__nonnull)eventMapping;
- (void)removeAnimatedEventFromView:(nonnull NSNumber *)viewTag
eventName:(nonnull NSString *)eventName
animatedNodeTag:(nonnull NSNumber *)animatedNodeTag;
- (void)handleAnimatedEvent:(nonnull id<RCTEvent>)event;
// listeners
- (void)startListeningToAnimatedNodeValue:(nonnull NSNumber *)tag
valueObserver:(nonnull id<RCTValueAnimatedNodeObserver>)valueObserver;
- (void)stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag;
@end

View File

@ -0,0 +1,466 @@
/*
* Copyright (c) Facebook, Inc. and its 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/RCTDiffClampAnimatedNode.h>
#import <React/RCTDivisionAnimatedNode.h>
#import <React/RCTEventAnimation.h>
#import <React/RCTFrameAnimation.h>
#import <React/RCTDecayAnimation.h>
#import <React/RCTInterpolationAnimatedNode.h>
#import <React/RCTModuloAnimatedNode.h>
#import <React/RCTMultiplicationAnimatedNode.h>
#import <React/RCTPropsAnimatedNode.h>
#import <React/RCTSpringAnimation.h>
#import <React/RCTStyleAnimatedNode.h>
#import <React/RCTSubtractionAnimatedNode.h>
#import <React/RCTTransformAnimatedNode.h>
#import <React/RCTValueAnimatedNode.h>
#import <React/RCTTrackingAnimatedNode.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;
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:(nonnull RCTBridge *)bridge
{
if ((self = [super init])) {
_bridge = bridge;
_animationNodes = [NSMutableDictionary new];
_eventDrivers = [NSMutableDictionary new];
_activeAnimations = [NSMutableSet new];
}
return self;
}
- (BOOL)isNodeManagedByFabric:(nonnull NSNumber *)tag
{
RCTAnimatedNode *node = _animationNodes[tag];
if (node) {
return [node isManagedByFabric];
}
return false;
}
#pragma mark -- Graph
- (void)createAnimatedNode:(nonnull 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],
@"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]};
});
NSString *nodeType = [RCTConvert NSString:config[@"type"]];
Class nodeClass = map[nodeType];
if (!nodeClass) {
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:(nonnull NSNumber *)parentTag
childTag:(nonnull 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:(nonnull NSNumber *)parentTag
childTag:(nonnull 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:(nonnull NSNumber *)nodeTag
viewTag:(nonnull NSNumber *)viewTag
viewName:(nonnull NSString *)viewName
{
RCTAnimatedNode *node = _animationNodes[nodeTag];
if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) {
[(RCTPropsAnimatedNode *)node connectToView:viewTag viewName:viewName bridge:_bridge];
}
[node setNeedsUpdate];
}
- (void)disconnectAnimatedNodeFromView:(nonnull NSNumber *)nodeTag
viewTag:(nonnull NSNumber *)viewTag
{
RCTAnimatedNode *node = _animationNodes[nodeTag];
if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) {
[(RCTPropsAnimatedNode *)node disconnectFromView:viewTag];
}
}
- (void)restoreDefaultValues:(nonnull 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:(nonnull NSNumber *)tag
{
RCTAnimatedNode *node = _animationNodes[tag];
if (node) {
[node detachNode];
[_animationNodes removeObjectForKey:tag];
}
}
#pragma mark -- Mutations
- (void)setAnimatedNodeValue:(nonnull NSNumber *)nodeTag
value:(nonnull 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:(nonnull NSNumber *)nodeTag
offset:(nonnull 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:(nonnull 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:(nonnull NSNumber *)nodeTag
{
RCTAnimatedNode *node = _animationNodes[nodeTag];
if (![node isKindOfClass:[RCTValueAnimatedNode class]]) {
RCTLogError(@"Not a value node.");
return;
}
RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node;
[valueNode extractOffset];
}
#pragma mark -- Drivers
- (void)startAnimatingNode:(nonnull NSNumber *)animationId
nodeTag:(nonnull NSNumber *)nodeTag
config:(NSDictionary<NSString *, id> *)config
endCallback:(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:(nonnull NSNumber *)animationId
{
for (id<RCTAnimationDriver> driver in _activeAnimations) {
if ([driver.animationId isEqual:animationId]) {
[driver stopAnimation];
[_activeAnimations removeObject:driver];
break;
}
}
}
- (void)stopAnimationsForNode:(nonnull 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:(nonnull NSNumber *)viewTag
eventName:(nonnull NSString *)eventName
eventMapping:(NSDictionary<NSString *, id> *)eventMapping
{
NSNumber *nodeTag = [RCTConvert NSNumber:eventMapping[@"animatedValueTag"]];
RCTAnimatedNode *node = _animationNodes[nodeTag];
if (!node) {
RCTLogError(@"Animated node with tag %@ does not exists", 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;
}
}
- (void)removeAnimatedEventFromView:(nonnull NSNumber *)viewTag
eventName:(nonnull NSString *)eventName
animatedNodeTag:(nonnull 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) {
for (RCTEventAnimation *driver in driversForKey) {
[self stopAnimationsForNode:driver.valueNode];
[driver updateWithEvent:event];
}
[self updateAnimations];
}
}
#pragma mark -- Listeners
- (void)startListeningToAnimatedNodeValue:(nonnull NSNumber *)tag
valueObserver:(id<RCTValueAnimatedNodeObserver>)valueObserver
{
RCTAnimatedNode *node = _animationNodes[tag];
if ([node isKindOfClass:[RCTValueAnimatedNode class]]) {
((RCTValueAnimatedNode *)node).valueObserver = valueObserver;
}
}
- (void)stopListeningToAnimatedNodeValue:(nonnull NSNumber *)tag
{
RCTAnimatedNode *node = _animationNodes[tag];
if ([node isKindOfClass:[RCTValueAnimatedNode class]]) {
((RCTValueAnimatedNode *)node).valueObserver = nil;
}
}
#pragma mark -- Animation Loop
- (void)startAnimationLoopIfNeeded
{
if (!_displayLink && _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) {
[_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];
}
#pragma mark -- Updates
- (void)updateAnimations
{
[_animationNodes enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, RCTAnimatedNode *node, BOOL *stop) {
if (node.needsUpdate) {
[node updateNodeIfNecessary];
}
}];
}
@end

View File

@ -0,0 +1,47 @@
# Copyright (c) Facebook, Inc. and its 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
else
source[:tag] = "v#{version}"
end
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
folly_version = '2020.01.13.00'
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 = "Facebook, Inc. and its affiliates"
s.platforms = { :ios => "10.0", :tvos => "10.0" }
s.compiler_flags = folly_compiler_flags + ' -Wno-nullability-completeness'
s.source = source
s.source_files = "{Drivers/*,Nodes/*,*}.{m,mm}"
s.preserve_paths = "package.json", "LICENSE", "LICENSE-docs"
s.header_dir = "RCTAnimation"
s.pod_target_xcconfig = {
"USE_HEADERMAP" => "YES",
"CLANG_CXX_LANGUAGE_STANDARD" => "c++14",
"HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/Folly\""
}
s.dependency "Folly", folly_version
s.dependency "RCTTypeSafety", version
s.dependency "ReactCommon/turbomodule/core", version
s.dependency "React-jsi", version
s.dependency "FBReactNativeSpec", version
s.dependency "React-Core/RCTAnimationHeaders", version
end