From 5ecdb252c3cc0ca15a70efa574f9a3ba974a3307 Mon Sep 17 00:00:00 2001 From: Brandon Withrow Date: Tue, 7 Jun 2016 20:40:54 -0700 Subject: [PATCH] Add support for native animations on iOS Summary: Currently on iOS animations are being performed on the JS thread. This ports animations over to the native thread and performs them natively. A lot of this work has already been done on Android, but this PR enables a few animation nodes that Android doesn't yet support such as Transform, Multiplication, and Addition nodes. Also there is a demo of the native animations added to the UIExplorer app. Closes https://github.com/facebook/react-native/pull/7884 Differential Revision: D3401811 Pulled By: nicklockwood fbshipit-source-id: c8d750b75e4410923e17eaeb6dcaf079a09942e2 --- .../UIExplorer/NativeAnimationsExample.js | 308 ++++++++++++++++ .../UIExplorer.xcodeproj/project.pbxproj | 30 ++ .../xcschemes/UIExplorer.xcscheme | 2 +- Examples/UIExplorer/UIExplorerList.ios.js | 4 + .../Animated/src/AnimatedImplementation.js | 34 +- .../Animated/src/NativeAnimatedHelper.js | 19 +- .../Nodes/RCTAdditionAnimatedNode.h | 14 + .../Nodes/RCTAdditionAnimatedNode.m | 27 ++ .../NativeAnimation/Nodes/RCTAnimatedNode.h | 54 +++ .../NativeAnimation/Nodes/RCTAnimatedNode.m | 135 +++++++ .../Nodes/RCTAnimationDriverNode.h | 40 +++ .../Nodes/RCTAnimationDriverNode.m | 138 +++++++ .../Nodes/RCTInterpolationAnimatedNode.h | 14 + .../Nodes/RCTInterpolationAnimatedNode.m | 97 +++++ .../Nodes/RCTMultiplicationAnimatedNode.h | 14 + .../Nodes/RCTMultiplicationAnimatedNode.m | 29 ++ .../Nodes/RCTPropsAnimatedNode.h | 24 ++ .../Nodes/RCTPropsAnimatedNode.m | 61 ++++ .../Nodes/RCTStyleAnimatedNode.h | 16 + .../Nodes/RCTStyleAnimatedNode.m | 61 ++++ .../Nodes/RCTTransformAnimatedNode.h | 16 + .../Nodes/RCTTransformAnimatedNode.m | 52 +++ .../Nodes/RCTValueAnimatedNode.h | 17 + .../Nodes/RCTValueAnimatedNode.m | 14 + .../RCTAnimation.xcodeproj/project.pbxproj | 337 ++++++++++++++++++ Libraries/NativeAnimation/RCTAnimationUtils.h | 22 ++ Libraries/NativeAnimation/RCTAnimationUtils.m | 32 ++ .../NativeAnimation/RCTNativeAnimatedModule.h | 13 + .../NativeAnimation/RCTNativeAnimatedModule.m | 247 +++++++++++++ .../NativeAnimation/RCTViewPropertyMapper.h | 22 ++ .../NativeAnimation/RCTViewPropertyMapper.m | 94 +++++ 31 files changed, 1983 insertions(+), 4 deletions(-) create mode 100644 Examples/UIExplorer/NativeAnimationsExample.js create mode 100644 Libraries/NativeAnimation/Nodes/RCTAdditionAnimatedNode.h create mode 100644 Libraries/NativeAnimation/Nodes/RCTAdditionAnimatedNode.m create mode 100644 Libraries/NativeAnimation/Nodes/RCTAnimatedNode.h create mode 100644 Libraries/NativeAnimation/Nodes/RCTAnimatedNode.m create mode 100644 Libraries/NativeAnimation/Nodes/RCTAnimationDriverNode.h create mode 100644 Libraries/NativeAnimation/Nodes/RCTAnimationDriverNode.m create mode 100644 Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.h create mode 100644 Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.m create mode 100644 Libraries/NativeAnimation/Nodes/RCTMultiplicationAnimatedNode.h create mode 100644 Libraries/NativeAnimation/Nodes/RCTMultiplicationAnimatedNode.m create mode 100644 Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.h create mode 100644 Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m create mode 100644 Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.h create mode 100644 Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.m create mode 100644 Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.h create mode 100644 Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.m create mode 100644 Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.h create mode 100644 Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.m create mode 100644 Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj create mode 100644 Libraries/NativeAnimation/RCTAnimationUtils.h create mode 100644 Libraries/NativeAnimation/RCTAnimationUtils.m create mode 100644 Libraries/NativeAnimation/RCTNativeAnimatedModule.h create mode 100644 Libraries/NativeAnimation/RCTNativeAnimatedModule.m create mode 100644 Libraries/NativeAnimation/RCTViewPropertyMapper.h create mode 100644 Libraries/NativeAnimation/RCTViewPropertyMapper.m diff --git a/Examples/UIExplorer/NativeAnimationsExample.js b/Examples/UIExplorer/NativeAnimationsExample.js new file mode 100644 index 000000000..67757f56e --- /dev/null +++ b/Examples/UIExplorer/NativeAnimationsExample.js @@ -0,0 +1,308 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + * + * The examples provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * @flow + */ +'use strict'; + +var React = require('react'); +var ReactNative = require('react-native'); +var { + View, + Text, + Animated, + StyleSheet, + TouchableWithoutFeedback, +} = ReactNative; +var UIExplorerButton = require('./UIExplorerButton'); + +var Tester = React.createClass({ + current: 0, + getInitialState() { + return { + native: new Animated.Value(0), + js: new Animated.Value(0), + }; + }, + + onPress() { + this.current = this.current ? 0 : 1; + const config = { + ...this.props.config, + toValue: this.current, + }; + try { + Animated[this.props.type](this.state.native, { ...config, useNativeDriver: true }).start(); + } catch (e) { + // uncomment this if you want to get the redbox errors! + throw e; + } + Animated[this.props.type](this.state.js, { ...config, useNativeDriver: false }).start(); + }, + + render() { + return ( + + + + Native: + + + {this.props.children(this.state.native)} + + + JavaScript: + + + {this.props.children(this.state.js)} + + + + ); + }, +}); + +const styles = StyleSheet.create({ + row: { + padding: 10, + zIndex: 1, + }, + block: { + width: 50, + height: 50, + backgroundColor: 'blue', + }, +}); + +exports.framework = 'React'; +exports.title = 'Native Animated Example'; +exports.description = 'Test out Native Animations'; + +exports.examples = [ +{ + title: 'Multistage With Multiply and rotation', + description: 'description', + render: function() { + return ( + + {anim => ( + + )} + + ); + }, + }, + { + title: 'Multistage With Multiply', + description: 'description', + render: function() { + return ( + + {anim => ( + + )} + + ); + }, + }, + { + title: 'Scale interpolation', + description: 'description', + render: function() { + return ( + + {anim => ( + + )} + + ); + }, + }, + { + title: 'Opacity without interpolation', + description: 'description', + render: function() { + return ( + + {anim => ( + + )} + + ); + }, + }, + { + title: 'Rotate interpolation', + description: 'description', + render: function() { + return ( + + {anim => ( + + )} + + ); + }, + }, + { + title: 'translateX => Animated.spring', + description: 'description', + render: function() { + return ( + + {anim => ( + + )} + + ); + }, + }, +]; diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj index ba1a02b73..f73a4214d 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj @@ -32,6 +32,7 @@ 13BCE84F1C9C209600DD7AAD /* RCTComponentPropsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13BCE84E1C9C209600DD7AAD /* RCTComponentPropsTests.m */; }; 13DB03481B5D2ED500C27245 /* RCTJSONTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DB03471B5D2ED500C27245 /* RCTJSONTests.m */; }; 13DF61B61B67A45000EDB188 /* RCTMethodArgumentTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */; }; + 13E501F11D07A84A005F35D8 /* libRCTAnimation.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 13E501A31D07A502005F35D8 /* libRCTAnimation.a */; }; 143BC5A11B21E45C00462512 /* UIExplorerSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 143BC5A01B21E45C00462512 /* UIExplorerSnapshotTests.m */; }; 144D21241B2204C5006DB32B /* RCTImageUtilTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 144D21231B2204C5006DB32B /* RCTImageUtilTests.m */; }; 147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 147CED4B1AB34F8C00DA3E4C /* libRCTActionSheet.a */; }; @@ -121,6 +122,13 @@ remoteGlobalIDString = 3C86DF461ADF2C930047B81A; remoteInfo = RCTWebSocket; }; + 13E501A21D07A502005F35D8 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 13E5019C1D07A502005F35D8 /* RCTAnimation.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 134814201AA4EA6300B7C361; + remoteInfo = RCTAnimation; + }; 143BC59B1B21E3E100462512 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; @@ -210,6 +218,7 @@ 13CC9D481AEED2B90020D1C2 /* RCTSettings.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTSettings.xcodeproj; path = ../../Libraries/Settings/RCTSettings.xcodeproj; sourceTree = ""; }; 13DB03471B5D2ED500C27245 /* RCTJSONTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTJSONTests.m; sourceTree = ""; }; 13DF61B51B67A45000EDB188 /* RCTMethodArgumentTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMethodArgumentTests.m; sourceTree = ""; }; + 13E5019C1D07A502005F35D8 /* RCTAnimation.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTAnimation.xcodeproj; path = ../../Libraries/NativeAnimation/RCTAnimation.xcodeproj; sourceTree = ""; }; 143BC57E1B21E18100462512 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 143BC5811B21E18100462512 /* testLayoutExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testLayoutExampleSnapshot_1@2x.png"; sourceTree = ""; }; 143BC5821B21E18100462512 /* testSliderExampleSnapshot_1@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "testSliderExampleSnapshot_1@2x.png"; sourceTree = ""; }; @@ -285,6 +294,7 @@ 14AADF051AC3DBB1002390C9 /* libReact.a in Frameworks */, 147CED4C1AB3532B00DA3E4C /* libRCTActionSheet.a in Frameworks */, 134454601AAFCABD003F0779 /* libRCTAdSupport.a in Frameworks */, + 13E501F11D07A84A005F35D8 /* libRCTAnimation.a in Frameworks */, 138DEE241B9EDFB6007F4EA5 /* libRCTCameraRoll.a in Frameworks */, 134A8A2A1AACED7A00945AAE /* libRCTGeolocation.a in Frameworks */, 13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */, @@ -315,6 +325,7 @@ 14AADEFF1AC3DB95002390C9 /* React.xcodeproj */, 14E0EEC81AB118F7000DECC3 /* RCTActionSheet.xcodeproj */, 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */, + 13E5019C1D07A502005F35D8 /* RCTAnimation.xcodeproj */, 138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */, 134A8A201AACED6A00945AAE /* RCTGeolocation.xcodeproj */, 13417FE31AA91428003F314A /* RCTImage.xcodeproj */, @@ -413,6 +424,14 @@ name = UIExplorer; sourceTree = ""; }; + 13E5019D1D07A502005F35D8 /* Products */ = { + isa = PBXGroup; + children = ( + 13E501A31D07A502005F35D8 /* libRCTAnimation.a */, + ); + name = Products; + sourceTree = ""; + }; 143BC57C1B21E18100462512 /* UIExplorerUnitTests */ = { isa = PBXGroup; children = ( @@ -693,6 +712,10 @@ ProductGroup = 134454561AAFCAAE003F0779 /* Products */; ProjectRef = 134454551AAFCAAE003F0779 /* RCTAdSupport.xcodeproj */; }, + { + ProductGroup = 13E5019D1D07A502005F35D8 /* Products */; + ProjectRef = 13E5019C1D07A502005F35D8 /* RCTAnimation.xcodeproj */; + }, { ProductGroup = 138DEE031B9EDDDB007F4EA5 /* Products */; ProjectRef = 138DEE021B9EDDDB007F4EA5 /* RCTCameraRoll.xcodeproj */; @@ -801,6 +824,13 @@ remoteRef = 139FDED81B0651EA00C62182 /* PBXContainerItemProxy */; sourceTree = BUILT_PRODUCTS_DIR; }; + 13E501A31D07A502005F35D8 /* libRCTAnimation.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libRCTAnimation.a; + remoteRef = 13E501A21D07A502005F35D8 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; 147CED4B1AB34F8C00DA3E4C /* libRCTActionSheet.a */ = { isa = PBXReferenceProxy; fileType = archive.ar; diff --git a/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme b/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme index 833aaaa4a..547d76ac5 100644 --- a/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme +++ b/Examples/UIExplorer/UIExplorer.xcodeproj/xcshareddata/xcschemes/UIExplorer.xcscheme @@ -1,6 +1,6 @@ = [ key: 'ModalExample', module: require('./ModalExample'), }, + { + key: 'NativeAnimationsExample', + module: require('./NativeAnimationsExample'), + }, { key: 'NavigatorExample', module: require('./Navigator/NavigatorExample'), diff --git a/Libraries/Animated/src/AnimatedImplementation.js b/Libraries/Animated/src/AnimatedImplementation.js index a91880505..9b767c649 100644 --- a/Libraries/Animated/src/AnimatedImplementation.js +++ b/Libraries/Animated/src/AnimatedImplementation.js @@ -243,7 +243,7 @@ class TimingAnimation extends Animation { this._duration = config.duration !== undefined ? config.duration : 500; this._delay = config.delay !== undefined ? config.delay : 0; this.__isInteraction = config.isInteraction !== undefined ? config.isInteraction : true; - this._useNativeDriver = !!config.useNativeDriver; + this._useNativeDriver = config.useNativeDriver !== undefined ? config.useNativeDriver : false; } _getNativeAnimationConfig(): any { @@ -256,6 +256,7 @@ class TimingAnimation extends Animation { type: 'frames', frames, toValue: this._toValue, + delay: this._delay }; } @@ -1086,6 +1087,18 @@ class AnimatedTransform extends AnimatedWithChildren { this._transforms = transforms; } + __makeNative() { + super.__makeNative(); + this._transforms.forEach(transform => { + for (var key in transform) { + var value = transform[key]; + if (value instanceof Animated) { + value.__makeNative(); + } + } + }); + } + __getValue(): Array { return this._transforms.map(transform => { var result = {}; @@ -1138,6 +1151,25 @@ class AnimatedTransform extends AnimatedWithChildren { } }); } + + __getNativeConfig(): any { + var transConfig = {}; + + this._transforms.forEach(transform => { + for (var key in transform) { + var value = transform[key]; + if (value instanceof Animated) { + transConfig[key] = value.__getNativeTag(); + } + } + }); + + NativeAnimatedHelper.validateTransform(transConfig); + return { + type: 'transform', + transform: transConfig, + }; + } } class AnimatedStyle extends AnimatedWithChildren { diff --git a/Libraries/Animated/src/NativeAnimatedHelper.js b/Libraries/Animated/src/NativeAnimatedHelper.js index 2e5cc68b1..befbad131 100644 --- a/Libraries/Animated/src/NativeAnimatedHelper.js +++ b/Libraries/Animated/src/NativeAnimatedHelper.js @@ -74,16 +74,22 @@ var API = { var PROPS_WHITELIST = { style: { opacity: true, - + transform: true, /* legacy android transform properties */ scaleX: true, scaleY: true, - rotation: true, translateX: true, translateY: true, }, }; +var TRANSFORM_WHITELIST = { + translateX: true, + translateY: true, + scale: true, + rotate: true, +}; + function validateProps(params: Object): void { for (var key in params) { if (!PROPS_WHITELIST.hasOwnProperty(key)) { @@ -92,6 +98,14 @@ function validateProps(params: Object): void { } } +function validateTransform(config: Object): void { + for (var key in config) { + if (!TRANSFORM_WHITELIST.hasOwnProperty(key)) { + throw new Error(`Property '${key}' is not supported by native animated module`); + } + } +} + function validateStyles(styles: Object): void { var STYLES_WHITELIST = PROPS_WHITELIST.style || {}; for (var key in styles) { @@ -129,6 +143,7 @@ module.exports = { API, validateProps, validateStyles, + validateTransform, validateInterpolation, generateNewNodeTag, generateNewAnimationId, diff --git a/Libraries/NativeAnimation/Nodes/RCTAdditionAnimatedNode.h b/Libraries/NativeAnimation/Nodes/RCTAdditionAnimatedNode.h new file mode 100644 index 000000000..83641afea --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTAdditionAnimatedNode.h @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTValueAnimatedNode.h" + +@interface RCTAdditionAnimatedNode : RCTValueAnimatedNode + +@end diff --git a/Libraries/NativeAnimation/Nodes/RCTAdditionAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTAdditionAnimatedNode.m new file mode 100644 index 000000000..400780955 --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTAdditionAnimatedNode.m @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTAdditionAnimatedNode.h" + +@implementation RCTAdditionAnimatedNode + +- (void)performUpdate +{ + [super performUpdate]; + NSArray *inputNodes = self.config[@"input"]; + if (inputNodes.count > 1) { + RCTValueAnimatedNode *parent1 = (RCTValueAnimatedNode *)self.parentNodes[inputNodes[0]]; + RCTValueAnimatedNode *parent2 = (RCTValueAnimatedNode *)self.parentNodes[inputNodes[1]]; + if (parent1 && parent2) { + self.value = parent1.value + parent2.value; + } + } +} + +@end diff --git a/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.h b/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.h new file mode 100644 index 000000000..8e06e9062 --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.h @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import + +@interface RCTAnimatedNode : NSObject + +- (instancetype)initWithTag:(NSNumber *)tag + config:(NSDictionary *)config NS_DESIGNATED_INITIALIZER; + +@property (nonatomic, readonly) NSNumber *nodeTag; +@property (nonatomic, copy, readonly) NSDictionary *config; + +@property (nonatomic, copy, readonly) NSDictionary *childNodes; +@property (nonatomic, copy, readonly) NSDictionary *parentNodes; + +@property (nonatomic, readonly) BOOL needsUpdate; +@property (nonatomic, readonly) BOOL hasUpdated; + +/** + * Marks a node and its children as needing update. + */ +- (void)setNeedsUpdate NS_REQUIRES_SUPER; + +/** + * The node will update its value if necesarry 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; + +/** + * Cleans up after a round of updates. + */ +- (void)cleanupAnimationUpdate 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 diff --git a/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.m new file mode 100644 index 000000000..9f5075b62 --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTAnimatedNode.m @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTAnimatedNode.h" + +#import "RCTDefines.h" + +@implementation RCTAnimatedNode +{ + NSMutableDictionary *_childNodes; + NSMutableDictionary *_parentNodes; +} + +- (instancetype)initWithTag:(NSNumber *)tag + config:(NSDictionary *)config +{ + if ((self = [super init])) { + _nodeTag = tag; + _config = [config copy]; + } + return self; +} + +RCT_NOT_IMPLEMENTED(- (instancetype)init) + +- (NSDictionary *)childNodes +{ + return _childNodes; +} + +- (NSDictionary *)parentNodes +{ + return _parentNodes; +} + +- (void)addChild:(RCTAnimatedNode *)child +{ + if (!_childNodes) { + _childNodes = [NSMutableDictionary new]; + } + if (child) { + _childNodes[child.nodeTag] = child; + [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 = [NSMutableDictionary new]; + } + if (parent) { + _parentNodes[parent.nodeTag] = parent; + } +} + +- (void)onDetachedFromNode:(RCTAnimatedNode *)parent +{ + if (!_parentNodes) { + return; + } + if (parent) { + [_parentNodes removeObjectForKey:parent.nodeTag]; + } +} + +- (void)detachNode +{ + for (RCTAnimatedNode *parent in _parentNodes.allValues) { + [parent removeChild:self]; + } + for (RCTAnimatedNode *child in _childNodes.allValues) { + [self removeChild:child]; + } +} + +- (void)setNeedsUpdate +{ + if (_needsUpdate) { + // Has already been marked. Stop branch. + return; + } + _needsUpdate = YES; + for (RCTAnimatedNode *child in _childNodes.allValues) { + [child setNeedsUpdate]; + } +} + +- (void)cleanupAnimationUpdate +{ + if (_hasUpdated) { + _needsUpdate = NO; + _hasUpdated = NO; + for (RCTAnimatedNode *child in _childNodes.allValues) { + [child cleanupAnimationUpdate]; + } + } +} + +- (void)updateNodeIfNecessary +{ + if (_needsUpdate && !_hasUpdated) { + for (RCTAnimatedNode *parent in _parentNodes.allValues) { + [parent updateNodeIfNecessary]; + } + [self performUpdate]; + } +} + +- (void)performUpdate +{ + _hasUpdated = YES; + // To be overidden by subclasses + // This method is called on a node only if it has been marked for update + // during the current update loop +} + +@end diff --git a/Libraries/NativeAnimation/Nodes/RCTAnimationDriverNode.h b/Libraries/NativeAnimation/Nodes/RCTAnimationDriverNode.h new file mode 100644 index 000000000..5f39ea465 --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTAnimationDriverNode.h @@ -0,0 +1,40 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import "RCTBridgeModule.h" + +@class RCTValueAnimatedNode; + +NS_ASSUME_NONNULL_BEGIN + +@interface RCTAnimationDriverNode : NSObject + +@property (nonatomic, readonly) NSNumber *animationId; +@property (nonatomic, readonly) NSNumber *outputValue; + +@property (nonatomic, readonly) BOOL animationHasBegun; +@property (nonatomic, readonly) BOOL animationHasFinished; + +- (instancetype)initWithId:(NSNumber *)animationId + delay:(NSTimeInterval)delay + toValue:(CGFloat)toValue + frames:(NSArray *)frames + forNode:(RCTValueAnimatedNode *)valueNode + callBack:(nullable RCTResponseSenderBlock)callback NS_DESIGNATED_INITIALIZER; + +- (void)startAnimation; +- (void)stopAnimation; +- (void)stepAnimation; +- (void)removeAnimation; +- (void)cleanupAnimationUpdate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/Libraries/NativeAnimation/Nodes/RCTAnimationDriverNode.m b/Libraries/NativeAnimation/Nodes/RCTAnimationDriverNode.m new file mode 100644 index 000000000..7da52da9c --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTAnimationDriverNode.m @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTAnimationDriverNode.h" + +#import + +#import "RCTAnimationUtils.h" +#import "RCTDefines.h" +#import "RCTValueAnimatedNode.h" + +const double SINGLE_FRAME_INTERVAL = 1.0 / 60.0; + +@implementation RCTAnimationDriverNode +{ + NSArray *_frames; + CGFloat _toValue; + CGFloat _fromValue; + NSTimeInterval _delay; + NSTimeInterval _animationStartTime; + NSTimeInterval _animationCurrentTime; + RCTValueAnimatedNode *_valueNode; + RCTResponseSenderBlock _callback; +} + +- (instancetype)initWithId:(nonnull NSNumber *)animationId + delay:(NSTimeInterval)delay + toValue:(CGFloat)toValue + frames:(nonnull NSArray *)frames + forNode:(nonnull RCTValueAnimatedNode *)valueNode + callBack:(nullable RCTResponseSenderBlock)callback +{ + if ((self = [super init])) { + _animationId = animationId; + _toValue = toValue; + _fromValue = valueNode.value; + _valueNode = valueNode; + _delay = delay; + _frames = [frames copy]; + _outputValue = @0; + _callback = [callback copy]; + } + return self; +} + +RCT_NOT_IMPLEMENTED(- (instancetype)init) + +- (void)startAnimation +{ + _animationStartTime = CACurrentMediaTime(); + _animationCurrentTime = _animationStartTime; + _animationHasBegun = YES; +} + +- (void)stopAnimation +{ + _animationHasFinished = YES; +} + +- (void)removeAnimation +{ + [self stopAnimation]; + _valueNode = nil; + if (_callback) { + _callback(@[(id)kCFNull]); + } +} + +- (void)stepAnimation +{ + if (!_animationHasBegun || + _animationHasFinished || + _frames.count == 0) { + // Animation has not begun or animation has already finished. + return; + } + + NSTimeInterval currentTime = CACurrentMediaTime(); + NSTimeInterval stepInterval = currentTime - _animationCurrentTime; + _animationCurrentTime = currentTime; + NSTimeInterval currentDuration = _animationCurrentTime - _animationStartTime; + + if (_delay > 0) { + // Decrement delay + _delay -= stepInterval; + return; + } + + // Determine how many frames have passed since last update. + // Get index of frames that surround the current interval + NSUInteger startIndex = floor(currentDuration / SINGLE_FRAME_INTERVAL); + NSUInteger nextIndex = startIndex + 1; + + if (nextIndex >= _frames.count) { + // We are at the end of the animation + // Update value and flag animation has ended. + NSNumber *finalValue = _frames.lastObject; + [self updateOutputWithFrameOutput:finalValue.doubleValue]; + [self stopAnimation]; + return; + } + + // Do a linear remap of the two frames to safegaurd against variable framerates + NSNumber *fromFrameValue = _frames[startIndex]; + NSNumber *toFrameValue = _frames[nextIndex]; + NSTimeInterval fromInterval = startIndex * SINGLE_FRAME_INTERVAL; + NSTimeInterval toInterval = nextIndex * SINGLE_FRAME_INTERVAL; + + // 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); + [self updateOutputWithFrameOutput:frameOutput]; +} + +- (void)updateOutputWithFrameOutput:(CGFloat)frameOutput +{ + CGFloat outputValue = RCTInterpolateValue(frameOutput, 0, 1, _fromValue, _toValue); + _outputValue = @(outputValue); + _valueNode.value = outputValue; + [_valueNode setNeedsUpdate]; +} + +- (void)cleanupAnimationUpdate +{ + [_valueNode cleanupAnimationUpdate]; +} + +@end diff --git a/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.h b/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.h new file mode 100644 index 000000000..2257e4964 --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.h @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTValueAnimatedNode.h" + +@interface RCTInterpolationAnimatedNode : RCTValueAnimatedNode + +@end diff --git a/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.m new file mode 100644 index 000000000..4c160ec24 --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTInterpolationAnimatedNode.m @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTInterpolationAnimatedNode.h" +#import "RCTAnimationUtils.h" + +@implementation RCTInterpolationAnimatedNode +{ + __weak RCTValueAnimatedNode *_parentNode; + NSArray *_inputRange; + NSArray *_outputRange; +} + +- (instancetype)initWithTag:(NSNumber *)tag + config:(NSDictionary *)config +{ + if ((self = [super initWithTag:tag config:config])) { + _inputRange = [config[@"inputRange"] copy]; + NSMutableArray *outputRange = [NSMutableArray array]; + for (id value in config[@"outputRange"]) { + if ([value isKindOfClass:[NSNumber class]]) { + [outputRange addObject:value]; + } else if ([value isKindOfClass:[NSString class]]) { + NSString *str = (NSString *)value; + if ([str hasSuffix:@"deg"]) { + double degrees = str.doubleValue; + [outputRange addObject:@(RCTDegreesToRadians(degrees))]; + } else { + // Assume radians + [outputRange addObject:@(str.doubleValue)]; + } + } + } + _outputRange = [outputRange copy]; + } + 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; + } +} + +- (NSUInteger)findIndexOfNearestValue:(CGFloat)value + inRange:(NSArray *)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; +} + +- (void)performUpdate +{ + [super performUpdate]; + if (!_parentNode) { + return; + } + + NSUInteger rangeIndex = [self findIndexOfNearestValue:_parentNode.value + inRange:_inputRange]; + NSNumber *inputMin = _inputRange[rangeIndex]; + NSNumber *inputMax = _inputRange[rangeIndex + 1]; + NSNumber *outputMin = _outputRange[rangeIndex]; + NSNumber *outputMax = _outputRange[rangeIndex + 1]; + + CGFloat outputValue = RCTInterpolateValue(_parentNode.value, + inputMin.doubleValue, + inputMax.doubleValue, + outputMin.doubleValue, + outputMax.doubleValue); + self.value = outputValue; +} + +@end diff --git a/Libraries/NativeAnimation/Nodes/RCTMultiplicationAnimatedNode.h b/Libraries/NativeAnimation/Nodes/RCTMultiplicationAnimatedNode.h new file mode 100644 index 000000000..43a1ff9b9 --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTMultiplicationAnimatedNode.h @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTValueAnimatedNode.h" + +@interface RCTMultiplicationAnimatedNode : RCTValueAnimatedNode + +@end diff --git a/Libraries/NativeAnimation/Nodes/RCTMultiplicationAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTMultiplicationAnimatedNode.m new file mode 100644 index 000000000..5a8117bdb --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTMultiplicationAnimatedNode.m @@ -0,0 +1,29 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTMultiplicationAnimatedNode.h" + +@implementation RCTMultiplicationAnimatedNode + +- (void)performUpdate +{ + [super performUpdate]; + + NSArray *inputNodes = self.config[@"input"]; + if (inputNodes.count > 1) { + RCTValueAnimatedNode *parent1 = (RCTValueAnimatedNode *)self.parentNodes[inputNodes[0]]; + RCTValueAnimatedNode *parent2 = (RCTValueAnimatedNode *)self.parentNodes[inputNodes[1]]; + if (!parent1 || !parent2) { + return; + } + self.value = parent1.value * parent2.value; + } +} + +@end diff --git a/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.h b/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.h new file mode 100644 index 000000000..08debc6c7 --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.h @@ -0,0 +1,24 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTAnimatedNode.h" + +@class RCTNativeAnimatedModule; +@class RCTViewPropertyMapper; + +@interface RCTPropsAnimatedNode : RCTAnimatedNode + +@property (nonatomic, readonly) RCTViewPropertyMapper *propertyMapper; + +- (void)connectToView:(NSNumber *)viewTag animatedModule:(RCTNativeAnimatedModule *)animationModule; +- (void)disconnectFromView:(NSNumber *)viewTag; + +- (void)performViewUpdatesIfNecessary; + +@end diff --git a/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m new file mode 100644 index 000000000..fcd5c34d6 --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTPropsAnimatedNode.m @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTPropsAnimatedNode.h" +#import "RCTAnimationUtils.h" +#import "RCTNativeAnimatedModule.h" +#import "RCTStyleAnimatedNode.h" +#import "RCTViewPropertyMapper.h" + +@implementation RCTPropsAnimatedNode +{ + RCTStyleAnimatedNode *_parentNode; +} + +- (void)onAttachedToNode:(RCTAnimatedNode *)parent +{ + [super onAttachedToNode:parent]; + if ([parent isKindOfClass:[RCTStyleAnimatedNode class]]) { + _parentNode = (RCTStyleAnimatedNode *)parent; + } +} + +- (void)onDetachedFromNode:(RCTAnimatedNode *)parent +{ + [super onDetachedFromNode:parent]; + if (_parentNode == parent) { + _parentNode = nil; + } +} + +- (void)connectToView:(NSNumber *)viewTag animatedModule:(RCTNativeAnimatedModule *)animationModule +{ + _propertyMapper = [[RCTViewPropertyMapper alloc] initWithViewTag:viewTag animationModule:animationModule]; +} + +- (void)disconnectFromView:(NSNumber *)viewTag +{ + _propertyMapper = nil; +} + +- (void)performUpdate +{ + [super performUpdate]; + [self performViewUpdatesIfNecessary]; +} + +- (void)performViewUpdatesIfNecessary +{ + NSDictionary *updates = [_parentNode updatedPropsDictionary]; + if (updates.count) { + [_propertyMapper updateViewWithDictionary:updates]; + } +} + +@end diff --git a/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.h b/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.h new file mode 100644 index 000000000..f3a6cd5df --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTAnimatedNode.h" + +@interface RCTStyleAnimatedNode : RCTAnimatedNode + +- (NSDictionary *)updatedPropsDictionary; + +@end diff --git a/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.m new file mode 100644 index 000000000..1910a1333 --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTStyleAnimatedNode.m @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTStyleAnimatedNode.h" +#import "RCTAnimationUtils.h" +#import "RCTValueAnimatedNode.h" +#import "RCTTransformAnimatedNode.h" + +@implementation RCTStyleAnimatedNode +{ + NSMutableDictionary *_updatedPropsDictionary; +} + +- (instancetype)initWithTag:(NSNumber *)tag + config:(NSDictionary *)config; +{ + if ((self = [super initWithTag:tag config:config])) { + _updatedPropsDictionary = [NSMutableDictionary new]; + } + return self; +} + +- (NSDictionary *)updatedPropsDictionary +{ + return _updatedPropsDictionary; +} + +- (void)performUpdate +{ + [super performUpdate]; + + NSDictionary *style = self.config[@"style"]; + [style enumerateKeysAndObjectsUsingBlock:^(NSString *property, NSNumber *nodeTag, __unused BOOL *stop) { + RCTAnimatedNode *node = self.parentNodes[nodeTag]; + if (node && node.hasUpdated) { + if ([node isKindOfClass:[RCTValueAnimatedNode class]]) { + RCTValueAnimatedNode *parentNode = (RCTValueAnimatedNode *)node; + [_updatedPropsDictionary setObject:@(parentNode.value) forKey:property]; + } else if ([node isKindOfClass:[RCTTransformAnimatedNode class]]) { + RCTTransformAnimatedNode *parentNode = (RCTTransformAnimatedNode *)node; + if (parentNode.updatedPropsDictionary.count) { + [_updatedPropsDictionary addEntriesFromDictionary:parentNode.updatedPropsDictionary]; + } + } + } + }]; +} + +- (void)cleanupAnimationUpdate +{ + [super cleanupAnimationUpdate]; + [_updatedPropsDictionary removeAllObjects]; +} + +@end diff --git a/Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.h b/Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.h new file mode 100644 index 000000000..6d1cfc840 --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.h @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTAnimatedNode.h" + +@interface RCTTransformAnimatedNode : RCTAnimatedNode + +- (NSDictionary *)updatedPropsDictionary; + +@end diff --git a/Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.m new file mode 100644 index 000000000..c6a356ff5 --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTTransformAnimatedNode.m @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTTransformAnimatedNode.h" +#import "RCTValueAnimatedNode.h" + +@implementation RCTTransformAnimatedNode +{ + NSMutableDictionary *_updatedPropsDictionary; +} + +- (instancetype)initWithTag:(NSNumber *)tag + config:(NSDictionary *)config; +{ + if ((self = [super initWithTag:tag config:config])) { + _updatedPropsDictionary = [NSMutableDictionary new]; + } + return self; +} + +- (NSDictionary *)updatedPropsDictionary +{ + return _updatedPropsDictionary; +} + +- (void)performUpdate +{ + [super performUpdate]; + + NSDictionary *transforms = self.config[@"transform"]; + [transforms enumerateKeysAndObjectsUsingBlock:^(NSString *property, NSNumber *nodeTag, __unused BOOL *stop) { + RCTAnimatedNode *node = self.parentNodes[nodeTag]; + if (node.hasUpdated && [node isKindOfClass:[RCTValueAnimatedNode class]]) { + RCTValueAnimatedNode *parentNode = (RCTValueAnimatedNode *)node; + _updatedPropsDictionary[property] = @(parentNode.value); + } + }]; +} + +- (void)cleanupAnimationUpdate +{ + [super cleanupAnimationUpdate]; + [_updatedPropsDictionary removeAllObjects]; +} + +@end diff --git a/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.h b/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.h new file mode 100644 index 000000000..af1832435 --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.h @@ -0,0 +1,17 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTAnimatedNode.h" +#import + +@interface RCTValueAnimatedNode : RCTAnimatedNode + +@property (nonatomic, assign) CGFloat value; + +@end diff --git a/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.m b/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.m new file mode 100644 index 000000000..cffa66681 --- /dev/null +++ b/Libraries/NativeAnimation/Nodes/RCTValueAnimatedNode.m @@ -0,0 +1,14 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTValueAnimatedNode.h" + +@implementation RCTValueAnimatedNode + +@end diff --git a/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj b/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj new file mode 100644 index 000000000..3f0c751b2 --- /dev/null +++ b/Libraries/NativeAnimation/RCTAnimation.xcodeproj/project.pbxproj @@ -0,0 +1,337 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 13E501CC1D07A644005F35D8 /* RCTAnimationUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501B81D07A644005F35D8 /* RCTAnimationUtils.m */; }; + 13E501CF1D07A644005F35D8 /* RCTNativeAnimatedModule.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501BE1D07A644005F35D8 /* RCTNativeAnimatedModule.m */; }; + 13E501D41D07A644005F35D8 /* RCTViewPropertyMapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501C81D07A644005F35D8 /* RCTViewPropertyMapper.m */; }; + 13E501E81D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501D71D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m */; }; + 13E501E91D07A6C9005F35D8 /* RCTAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501D91D07A6C9005F35D8 /* RCTAnimatedNode.m */; }; + 13E501EA1D07A6C9005F35D8 /* RCTAnimationDriverNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501DB1D07A6C9005F35D8 /* RCTAnimationDriverNode.m */; }; + 13E501EB1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501DD1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m */; }; + 13E501EC1D07A6C9005F35D8 /* RCTMultiplicationAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501DF1D07A6C9005F35D8 /* RCTMultiplicationAnimatedNode.m */; }; + 13E501ED1D07A6C9005F35D8 /* RCTPropsAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E11D07A6C9005F35D8 /* RCTPropsAnimatedNode.m */; }; + 13E501EE1D07A6C9005F35D8 /* RCTStyleAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E31D07A6C9005F35D8 /* RCTStyleAnimatedNode.m */; }; + 13E501EF1D07A6C9005F35D8 /* RCTTransformAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E51D07A6C9005F35D8 /* RCTTransformAnimatedNode.m */; }; + 13E501F01D07A6C9005F35D8 /* RCTValueAnimatedNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 13E501E71D07A6C9005F35D8 /* RCTValueAnimatedNode.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 58B511D91A9E6C8500147676 /* CopyFiles */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = "include/$(PRODUCT_NAME)"; + dstSubfolderSpec = 16; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 134814201AA4EA6300B7C361 /* libRCTAnimation.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTAnimation.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 13E501B71D07A644005F35D8 /* RCTAnimationUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimationUtils.h; sourceTree = ""; }; + 13E501B81D07A644005F35D8 /* RCTAnimationUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAnimationUtils.m; sourceTree = ""; }; + 13E501BD1D07A644005F35D8 /* RCTNativeAnimatedModule.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNativeAnimatedModule.h; sourceTree = ""; }; + 13E501BE1D07A644005F35D8 /* RCTNativeAnimatedModule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNativeAnimatedModule.m; sourceTree = ""; }; + 13E501C71D07A644005F35D8 /* RCTViewPropertyMapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTViewPropertyMapper.h; sourceTree = ""; }; + 13E501C81D07A644005F35D8 /* RCTViewPropertyMapper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTViewPropertyMapper.m; sourceTree = ""; }; + 13E501D61D07A6C9005F35D8 /* RCTAdditionAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAdditionAnimatedNode.h; sourceTree = ""; }; + 13E501D71D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAdditionAnimatedNode.m; sourceTree = ""; }; + 13E501D81D07A6C9005F35D8 /* RCTAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimatedNode.h; sourceTree = ""; }; + 13E501D91D07A6C9005F35D8 /* RCTAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAnimatedNode.m; sourceTree = ""; }; + 13E501DA1D07A6C9005F35D8 /* RCTAnimationDriverNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTAnimationDriverNode.h; sourceTree = ""; }; + 13E501DB1D07A6C9005F35D8 /* RCTAnimationDriverNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTAnimationDriverNode.m; sourceTree = ""; }; + 13E501DC1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTInterpolationAnimatedNode.h; sourceTree = ""; }; + 13E501DD1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTInterpolationAnimatedNode.m; sourceTree = ""; }; + 13E501DE1D07A6C9005F35D8 /* RCTMultiplicationAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTMultiplicationAnimatedNode.h; sourceTree = ""; }; + 13E501DF1D07A6C9005F35D8 /* RCTMultiplicationAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTMultiplicationAnimatedNode.m; sourceTree = ""; }; + 13E501E01D07A6C9005F35D8 /* RCTPropsAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTPropsAnimatedNode.h; sourceTree = ""; }; + 13E501E11D07A6C9005F35D8 /* RCTPropsAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTPropsAnimatedNode.m; sourceTree = ""; }; + 13E501E21D07A6C9005F35D8 /* RCTStyleAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTStyleAnimatedNode.h; sourceTree = ""; }; + 13E501E31D07A6C9005F35D8 /* RCTStyleAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTStyleAnimatedNode.m; sourceTree = ""; }; + 13E501E41D07A6C9005F35D8 /* RCTTransformAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTransformAnimatedNode.h; sourceTree = ""; }; + 13E501E51D07A6C9005F35D8 /* RCTTransformAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTransformAnimatedNode.m; sourceTree = ""; }; + 13E501E61D07A6C9005F35D8 /* RCTValueAnimatedNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTValueAnimatedNode.h; sourceTree = ""; }; + 13E501E71D07A6C9005F35D8 /* RCTValueAnimatedNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTValueAnimatedNode.m; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 58B511D81A9E6C8500147676 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 134814211AA4EA7D00B7C361 /* Products */ = { + isa = PBXGroup; + children = ( + 134814201AA4EA6300B7C361 /* libRCTAnimation.a */, + ); + name = Products; + sourceTree = ""; + }; + 13E501D51D07A6C9005F35D8 /* Nodes */ = { + isa = PBXGroup; + children = ( + 13E501D61D07A6C9005F35D8 /* RCTAdditionAnimatedNode.h */, + 13E501D71D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m */, + 13E501D81D07A6C9005F35D8 /* RCTAnimatedNode.h */, + 13E501D91D07A6C9005F35D8 /* RCTAnimatedNode.m */, + 13E501DA1D07A6C9005F35D8 /* RCTAnimationDriverNode.h */, + 13E501DB1D07A6C9005F35D8 /* RCTAnimationDriverNode.m */, + 13E501DC1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.h */, + 13E501DD1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m */, + 13E501DE1D07A6C9005F35D8 /* RCTMultiplicationAnimatedNode.h */, + 13E501DF1D07A6C9005F35D8 /* RCTMultiplicationAnimatedNode.m */, + 13E501E01D07A6C9005F35D8 /* RCTPropsAnimatedNode.h */, + 13E501E11D07A6C9005F35D8 /* RCTPropsAnimatedNode.m */, + 13E501E21D07A6C9005F35D8 /* RCTStyleAnimatedNode.h */, + 13E501E31D07A6C9005F35D8 /* RCTStyleAnimatedNode.m */, + 13E501E41D07A6C9005F35D8 /* RCTTransformAnimatedNode.h */, + 13E501E51D07A6C9005F35D8 /* RCTTransformAnimatedNode.m */, + 13E501E61D07A6C9005F35D8 /* RCTValueAnimatedNode.h */, + 13E501E71D07A6C9005F35D8 /* RCTValueAnimatedNode.m */, + ); + path = Nodes; + sourceTree = ""; + }; + 58B511D21A9E6C8500147676 = { + isa = PBXGroup; + children = ( + 13E501B71D07A644005F35D8 /* RCTAnimationUtils.h */, + 13E501B81D07A644005F35D8 /* RCTAnimationUtils.m */, + 13E501C71D07A644005F35D8 /* RCTViewPropertyMapper.h */, + 13E501C81D07A644005F35D8 /* RCTViewPropertyMapper.m */, + 13E501BD1D07A644005F35D8 /* RCTNativeAnimatedModule.h */, + 13E501BE1D07A644005F35D8 /* RCTNativeAnimatedModule.m */, + 13E501D51D07A6C9005F35D8 /* Nodes */, + 134814211AA4EA7D00B7C361 /* Products */, + ); + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 58B511DA1A9E6C8500147676 /* RCTAnimation */ = { + isa = PBXNativeTarget; + buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTAnimation" */; + buildPhases = ( + 58B511D71A9E6C8500147676 /* Sources */, + 58B511D81A9E6C8500147676 /* Frameworks */, + 58B511D91A9E6C8500147676 /* CopyFiles */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = RCTAnimation; + productName = RCTDataManager; + productReference = 134814201AA4EA6300B7C361 /* libRCTAnimation.a */; + productType = "com.apple.product-type.library.static"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 58B511D31A9E6C8500147676 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0730; + ORGANIZATIONNAME = Facebook; + TargetAttributes = { + 58B511DA1A9E6C8500147676 = { + CreatedOnToolsVersion = 6.1.1; + }; + }; + }; + buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTAnimation" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 58B511D21A9E6C8500147676; + productRefGroup = 58B511D21A9E6C8500147676; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 58B511DA1A9E6C8500147676 /* RCTAnimation */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXSourcesBuildPhase section */ + 58B511D71A9E6C8500147676 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 13E501F01D07A6C9005F35D8 /* RCTValueAnimatedNode.m in Sources */, + 13E501EE1D07A6C9005F35D8 /* RCTStyleAnimatedNode.m in Sources */, + 13E501CC1D07A644005F35D8 /* RCTAnimationUtils.m in Sources */, + 13E501CF1D07A644005F35D8 /* RCTNativeAnimatedModule.m in Sources */, + 13E501EC1D07A6C9005F35D8 /* RCTMultiplicationAnimatedNode.m in Sources */, + 13E501ED1D07A6C9005F35D8 /* RCTPropsAnimatedNode.m in Sources */, + 13E501E91D07A6C9005F35D8 /* RCTAnimatedNode.m in Sources */, + 13E501EB1D07A6C9005F35D8 /* RCTInterpolationAnimatedNode.m in Sources */, + 13E501E81D07A6C9005F35D8 /* RCTAdditionAnimatedNode.m in Sources */, + 13E501EA1D07A6C9005F35D8 /* RCTAnimationDriverNode.m in Sources */, + 13E501EF1D07A6C9005F35D8 /* RCTTransformAnimatedNode.m in Sources */, + 13E501D41D07A644005F35D8 /* RCTViewPropertyMapper.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 58B511ED1A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + WARNING_CFLAGS = ( + "-Werror", + "-Wall", + ); + }; + name = Debug; + }; + 58B511EE1A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 7.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + WARNING_CFLAGS = ( + "-Werror", + "-Wall", + ); + }; + name = Release; + }; + 58B511F01A9E6C8500147676 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../React/**", + ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = RCTAnimation; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 58B511F11A9E6C8500147676 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + HEADER_SEARCH_PATHS = ( + "$(inherited)", + /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, + "$(SRCROOT)/../../React/**", + ); + LIBRARY_SEARCH_PATHS = "$(inherited)"; + OTHER_LDFLAGS = "-ObjC"; + PRODUCT_NAME = RCTAnimation; + SKIP_INSTALL = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTAnimation" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511ED1A9E6C8500147676 /* Debug */, + 58B511EE1A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTAnimation" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 58B511F01A9E6C8500147676 /* Debug */, + 58B511F11A9E6C8500147676 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 58B511D31A9E6C8500147676 /* Project object */; +} diff --git a/Libraries/NativeAnimation/RCTAnimationUtils.h b/Libraries/NativeAnimation/RCTAnimationUtils.h new file mode 100644 index 000000000..f34a9c6ee --- /dev/null +++ b/Libraries/NativeAnimation/RCTAnimationUtils.h @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import +#import + +#import "RCTDefines.h" + +RCT_EXTERN CGFloat RCTInterpolateValue(CGFloat value, + CGFloat fromMin, + CGFloat fromMax, + CGFloat toMin, + CGFloat toMax); + +RCT_EXTERN CGFloat RCTRadiansToDegrees(CGFloat radians); +RCT_EXTERN CGFloat RCTDegreesToRadians(CGFloat degrees); diff --git a/Libraries/NativeAnimation/RCTAnimationUtils.m b/Libraries/NativeAnimation/RCTAnimationUtils.m new file mode 100644 index 000000000..720edddbd --- /dev/null +++ b/Libraries/NativeAnimation/RCTAnimationUtils.m @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTAnimationUtils.h" + +/** + * Interpolates value by remapping it linearly fromMin->fromMax to toMin->toMax + */ +CGFloat RCTInterpolateValue(CGFloat value, + CGFloat fromMin, + CGFloat fromMax, + CGFloat toMin, + CGFloat toMax) +{ + return toMin + (value - fromMin) * (toMax - toMin) / (fromMax - fromMin); +} + +CGFloat RCTRadiansToDegrees(CGFloat radians) +{ + return radians * 180.0 / M_PI; +} + +CGFloat RCTDegreesToRadians(CGFloat degrees) +{ + return degrees / 180.0 * M_PI; +} diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedModule.h b/Libraries/NativeAnimation/RCTNativeAnimatedModule.h new file mode 100644 index 000000000..d099ac53b --- /dev/null +++ b/Libraries/NativeAnimation/RCTNativeAnimatedModule.h @@ -0,0 +1,13 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +#import "RCTBridgeModule.h" + +@interface RCTNativeAnimatedModule : NSObject + +@end diff --git a/Libraries/NativeAnimation/RCTNativeAnimatedModule.m b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m new file mode 100644 index 000000000..74e157fc1 --- /dev/null +++ b/Libraries/NativeAnimation/RCTNativeAnimatedModule.m @@ -0,0 +1,247 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +#import "RCTNativeAnimatedModule.h" + +#import "RCTAdditionAnimatedNode.h" +#import "RCTAnimationDriverNode.h" +#import "RCTAnimationUtils.h" +#import "RCTBridge.h" +#import "RCTConvert.h" +#import "RCTInterpolationAnimatedNode.h" +#import "RCTLog.h" +#import "RCTMultiplicationAnimatedNode.h" +#import "RCTPropsAnimatedNode.h" +#import "RCTStyleAnimatedNode.h" +#import "RCTTransformAnimatedNode.h" +#import "RCTValueAnimatedNode.h" + +@implementation RCTNativeAnimatedModule +{ + NSMutableDictionary *_animationNodes; + NSMutableDictionary *_animationDrivers; + NSMutableSet *_activeAnimations; + NSMutableSet *_finishedAnimations; + NSMutableSet *_updatedValueNodes; + NSMutableSet *_propAnimationNodes; + CADisplayLink *_displayLink; +} + +@synthesize bridge = _bridge; + +RCT_EXPORT_MODULE() + +- (void)setBridge:(RCTBridge *)bridge +{ + _bridge = bridge; + _animationNodes = [NSMutableDictionary new]; + _animationDrivers = [NSMutableDictionary new]; + _activeAnimations = [NSMutableSet new]; + _finishedAnimations = [NSMutableSet new]; + _updatedValueNodes = [NSMutableSet new]; + _propAnimationNodes = [NSMutableSet new]; +} + +RCT_EXPORT_METHOD(createAnimatedNode:(nonnull NSNumber *)tag + config:(NSDictionary *)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], + @"multiplication" : [RCTMultiplicationAnimatedNode class], + @"transform" : [RCTTransformAnimatedNode 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]; + _animationNodes[tag] = node; + + if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) { + [_propAnimationNodes addObject:(RCTPropsAnimatedNode *)node]; + } +} + +RCT_EXPORT_METHOD(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]; +} + +RCT_EXPORT_METHOD(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]; +} + +RCT_EXPORT_METHOD(startAnimatingNode:(nonnull NSNumber *)animationId + nodeTag:(nonnull NSNumber *)nodeTag + config:(NSDictionary *)config + endCallback:(RCTResponseSenderBlock)callBack) +{ + if (RCT_DEBUG && ![config[@"type"] isEqual:@"frames"]) { + RCTLogError(@"Unsupported animation type: %@", config[@"type"]); + return; + } + + NSTimeInterval delay = [RCTConvert double:config[@"delay"]]; + NSNumber *toValue = [RCTConvert NSNumber:config[@"toValue"]] ?: @1; + NSArray *frames = [RCTConvert NSNumberArray:config[@"frames"]]; + + RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)_animationNodes[nodeTag]; + + RCTAnimationDriverNode *animationDriver = + [[RCTAnimationDriverNode alloc] initWithId:animationId + delay:delay + toValue:toValue.doubleValue + frames:frames + forNode:valueNode + callBack:callBack]; + [_activeAnimations addObject:animationDriver]; + _animationDrivers[animationId] = animationDriver; + [animationDriver startAnimation]; + [self startAnimation]; +} + +RCT_EXPORT_METHOD(stopAnimation:(nonnull NSNumber *)animationId) +{ + RCTAnimationDriverNode *driver = _animationDrivers[animationId]; + if (driver) { + [driver removeAnimation]; + [_animationDrivers removeObjectForKey:animationId]; + [_activeAnimations removeObject:driver]; + [_finishedAnimations removeObject:driver]; + } +} + +RCT_EXPORT_METHOD(setAnimatedNodeValue:(nonnull NSNumber *)nodeTag + value:(nonnull NSNumber *)value) +{ + RCTAnimatedNode *node = _animationNodes[nodeTag]; + if (![node isKindOfClass:[RCTValueAnimatedNode class]]) { + RCTLogError(@"Not a value node."); + return; + } + RCTValueAnimatedNode *valueNode = (RCTValueAnimatedNode *)node; + valueNode.value = value.floatValue; + [valueNode setNeedsUpdate]; +} + +RCT_EXPORT_METHOD(connectAnimatedNodeToView:(nonnull NSNumber *)nodeTag + viewTag:(nonnull NSNumber *)viewTag) +{ + RCTAnimatedNode *node = _animationNodes[nodeTag]; + if (viewTag && [node isKindOfClass:[RCTPropsAnimatedNode class]]) { + [(RCTPropsAnimatedNode *)node connectToView:viewTag animatedModule:self]; + } +} + +RCT_EXPORT_METHOD(disconnectAnimatedNodeFromView:(nonnull NSNumber *)nodeTag + viewTag:(nonnull NSNumber *)viewTag) +{ + RCTAnimatedNode *node = _animationNodes[nodeTag]; + if (viewTag && node && [node isKindOfClass:[RCTPropsAnimatedNode class]]) { + [(RCTPropsAnimatedNode *)node disconnectFromView:viewTag]; + } +} + +RCT_EXPORT_METHOD(dropAnimatedNode:(nonnull NSNumber *)tag) +{ + RCTAnimatedNode *node = _animationNodes[tag]; + if (node) { + [node detachNode]; + [_animationNodes removeObjectForKey:tag]; + if ([node isKindOfClass:[RCTValueAnimatedNode class]]) { + [_updatedValueNodes removeObject:(RCTValueAnimatedNode *)node]; + } else if ([node isKindOfClass:[RCTPropsAnimatedNode class]]) { + [_propAnimationNodes removeObject:(RCTPropsAnimatedNode *)node]; + } + } +} + +#pragma mark -- Animation Loop + +- (void)startAnimation +{ + if (!_displayLink && _activeAnimations.count > 0) { + _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(updateAnimations)]; + [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; + } +} + +- (void)updateAnimations +{ + // Step Current active animations + // This also recursively marks children nodes as needing update + for (RCTAnimationDriverNode *animationDriver in _activeAnimations) { + [animationDriver stepAnimation]; + } + + // Perform node updates for marked nodes. + // At this point all nodes that are in need of an update are properly marked as such. + for (RCTPropsAnimatedNode *propsNode in _propAnimationNodes) { + [propsNode updateNodeIfNecessary]; + } + + // Cleanup nodes and prepare for next cycle. Remove updated nodes from bucket. + for (RCTAnimationDriverNode *driverNode in _activeAnimations) { + [driverNode cleanupAnimationUpdate]; + } + for (RCTValueAnimatedNode *valueNode in _updatedValueNodes) { + [valueNode cleanupAnimationUpdate]; + } + [_updatedValueNodes removeAllObjects]; + + for (RCTAnimationDriverNode *driverNode in _activeAnimations) { + if (driverNode.animationHasFinished) { + [driverNode removeAnimation]; + [_finishedAnimations addObject:driverNode]; + } + } + for (RCTAnimationDriverNode *driverNode in _finishedAnimations) { + [_activeAnimations removeObject:driverNode]; + [_animationDrivers removeObjectForKey:driverNode.animationId]; + } + [_finishedAnimations removeAllObjects]; + + if (_activeAnimations.count == 0) { + [_displayLink invalidate]; + _displayLink = nil; + } +} + +@end diff --git a/Libraries/NativeAnimation/RCTViewPropertyMapper.h b/Libraries/NativeAnimation/RCTViewPropertyMapper.h new file mode 100644 index 000000000..29bbee754 --- /dev/null +++ b/Libraries/NativeAnimation/RCTViewPropertyMapper.h @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ +#import + +@class RCTNativeAnimatedModule; + +@interface RCTViewPropertyMapper : NSObject + +@property (nonatomic, readonly) NSNumber *viewTag; + +- (instancetype)initWithViewTag:(NSNumber *)viewTag + animationModule:(RCTNativeAnimatedModule *)animationModule NS_DESIGNATED_INITIALIZER; + +- (void)updateViewWithDictionary:(NSDictionary *)updates; + +@end diff --git a/Libraries/NativeAnimation/RCTViewPropertyMapper.m b/Libraries/NativeAnimation/RCTViewPropertyMapper.m new file mode 100644 index 000000000..bfa9803a6 --- /dev/null +++ b/Libraries/NativeAnimation/RCTViewPropertyMapper.m @@ -0,0 +1,94 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +#import "RCTViewPropertyMapper.h" + +#import + +#import "RCTBridge.h" +#import "RCTUIManager.h" +#import "RCTNativeAnimatedModule.h" + +@implementation RCTViewPropertyMapper +{ + CGFloat _translateX; + CGFloat _translateY; + CGFloat _scaleX; + CGFloat _scaleY; + CGFloat _rotation; + RCTNativeAnimatedModule *_animationModule; +} + +- (instancetype)initWithViewTag:(NSNumber *)viewTag + animationModule:(RCTNativeAnimatedModule *)animationModule +{ + if ((self = [super init])) { + _animationModule = animationModule; + _viewTag = viewTag; + _translateX = 0; + _translateY = 0; + _scaleX = 1; + _scaleY = 1; + _rotation = 0; + } + return self; +} + +RCT_NOT_IMPLEMENTED(- (instancetype)init) + +- (void)updateViewWithDictionary:(NSDictionary *)updates +{ + if (updates.count) { + UIView *view = [_animationModule.bridge.uiManager viewForReactTag:_viewTag]; + if (!view) { + return; + } + + NSNumber *opacity = updates[@"opacity"]; + if (opacity) { + view.alpha = opacity.doubleValue; + } + + NSNumber *scale = updates[@"scale"]; + if (scale) { + _scaleX = scale.doubleValue; + _scaleY = scale.doubleValue; + } + NSNumber *scaleX = updates[@"scaleX"]; + if (scaleX) { + _scaleX = scaleX.doubleValue; + } + NSNumber *scaleY = updates[@"scaleY"]; + if (scaleY) { + _scaleY = scaleY.doubleValue; + } + NSNumber *translateX = updates[@"translateX"]; + if (translateX) { + _translateX = translateX.doubleValue; + } + NSNumber *translateY = updates[@"translateY"]; + if (translateY) { + _translateY = translateY.doubleValue; + } + NSNumber *rotation = updates[@"rotate"]; + if (rotation) { + _rotation = rotation.doubleValue; + } + + if (translateX || translateY || scale || scaleX || scaleY || rotation) { + CATransform3D xform = CATransform3DMakeScale(_scaleX, _scaleY, 0); + xform = CATransform3DTranslate(xform, _translateX, _translateY, 0); + xform = CATransform3DRotate(xform, _rotation, 0, 0, 1); + view.layer.allowsEdgeAntialiasing = YES; + view.layer.transform = xform; + } + } +} + +@end