mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
progress
This commit is contained in:
@@ -1,7 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
// This folder currently runs through babel and doesn't need to be
|
||||
// compatible with node 4
|
||||
"comma-dangle": 0
|
||||
}
|
||||
}
|
||||
@@ -1,491 +0,0 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
0CF68B051AF0549300FF9E5C /* ARTGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68ADE1AF0549300FF9E5C /* ARTGroup.m */; };
|
||||
0CF68B061AF0549300FF9E5C /* ARTNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE01AF0549300FF9E5C /* ARTNode.m */; };
|
||||
0CF68B071AF0549300FF9E5C /* ARTRenderable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE21AF0549300FF9E5C /* ARTRenderable.m */; };
|
||||
0CF68B081AF0549300FF9E5C /* ARTShape.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE41AF0549300FF9E5C /* ARTShape.m */; };
|
||||
0CF68B091AF0549300FF9E5C /* ARTSurfaceView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE61AF0549300FF9E5C /* ARTSurfaceView.m */; };
|
||||
0CF68B0A1AF0549300FF9E5C /* ARTText.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE81AF0549300FF9E5C /* ARTText.m */; };
|
||||
0CF68B0B1AF0549300FF9E5C /* ARTBrush.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AEC1AF0549300FF9E5C /* ARTBrush.m */; };
|
||||
0CF68B0C1AF0549300FF9E5C /* ARTLinearGradient.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AEE1AF0549300FF9E5C /* ARTLinearGradient.m */; };
|
||||
0CF68B0D1AF0549300FF9E5C /* ARTPattern.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF01AF0549300FF9E5C /* ARTPattern.m */; };
|
||||
0CF68B0E1AF0549300FF9E5C /* ARTRadialGradient.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF21AF0549300FF9E5C /* ARTRadialGradient.m */; };
|
||||
0CF68B0F1AF0549300FF9E5C /* ARTSolidColor.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF41AF0549300FF9E5C /* ARTSolidColor.m */; };
|
||||
0CF68B101AF0549300FF9E5C /* RCTConvert+ART.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF71AF0549300FF9E5C /* RCTConvert+ART.m */; };
|
||||
0CF68B111AF0549300FF9E5C /* ARTGroupManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AFA1AF0549300FF9E5C /* ARTGroupManager.m */; };
|
||||
0CF68B121AF0549300FF9E5C /* ARTNodeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AFC1AF0549300FF9E5C /* ARTNodeManager.m */; };
|
||||
0CF68B131AF0549300FF9E5C /* ARTRenderableManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AFE1AF0549300FF9E5C /* ARTRenderableManager.m */; };
|
||||
0CF68B141AF0549300FF9E5C /* ARTShapeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68B001AF0549300FF9E5C /* ARTShapeManager.m */; };
|
||||
0CF68B151AF0549300FF9E5C /* ARTSurfaceViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68B021AF0549300FF9E5C /* ARTSurfaceViewManager.m */; };
|
||||
0CF68B161AF0549300FF9E5C /* ARTTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68B041AF0549300FF9E5C /* ARTTextManager.m */; };
|
||||
325CF7AD1E5F2ABA00AC9606 /* ARTBrush.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AEC1AF0549300FF9E5C /* ARTBrush.m */; };
|
||||
325CF7AE1E5F2ABA00AC9606 /* ARTLinearGradient.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AEE1AF0549300FF9E5C /* ARTLinearGradient.m */; };
|
||||
325CF7AF1E5F2ABA00AC9606 /* ARTPattern.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF01AF0549300FF9E5C /* ARTPattern.m */; };
|
||||
325CF7B01E5F2ABA00AC9606 /* ARTRadialGradient.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF21AF0549300FF9E5C /* ARTRadialGradient.m */; };
|
||||
325CF7B11E5F2ABA00AC9606 /* ARTSolidColor.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF41AF0549300FF9E5C /* ARTSolidColor.m */; };
|
||||
325CF7B21E5F2ABA00AC9606 /* ARTGroupManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AFA1AF0549300FF9E5C /* ARTGroupManager.m */; };
|
||||
325CF7B31E5F2ABA00AC9606 /* ARTNodeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AFC1AF0549300FF9E5C /* ARTNodeManager.m */; };
|
||||
325CF7B41E5F2ABA00AC9606 /* ARTRenderableManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AFE1AF0549300FF9E5C /* ARTRenderableManager.m */; };
|
||||
325CF7B51E5F2ABA00AC9606 /* ARTShapeManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68B001AF0549300FF9E5C /* ARTShapeManager.m */; };
|
||||
325CF7B61E5F2ABA00AC9606 /* ARTSurfaceViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68B021AF0549300FF9E5C /* ARTSurfaceViewManager.m */; };
|
||||
325CF7B71E5F2ABA00AC9606 /* ARTTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68B041AF0549300FF9E5C /* ARTTextManager.m */; };
|
||||
325CF7B81E5F2ABA00AC9606 /* ARTGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68ADE1AF0549300FF9E5C /* ARTGroup.m */; };
|
||||
325CF7B91E5F2ABA00AC9606 /* ARTNode.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE01AF0549300FF9E5C /* ARTNode.m */; };
|
||||
325CF7BA1E5F2ABA00AC9606 /* ARTRenderable.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE21AF0549300FF9E5C /* ARTRenderable.m */; };
|
||||
325CF7BB1E5F2ABA00AC9606 /* ARTShape.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE41AF0549300FF9E5C /* ARTShape.m */; };
|
||||
325CF7BC1E5F2ABA00AC9606 /* ARTSurfaceView.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE61AF0549300FF9E5C /* ARTSurfaceView.m */; };
|
||||
325CF7BD1E5F2ABA00AC9606 /* ARTText.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AE81AF0549300FF9E5C /* ARTText.m */; };
|
||||
325CF7BE1E5F2ABA00AC9606 /* RCTConvert+ART.m in Sources */ = {isa = PBXBuildFile; fileRef = 0CF68AF71AF0549300FF9E5C /* RCTConvert+ART.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
0CF68ABF1AF0540F00FF9E5C /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "include/$(PRODUCT_NAME)";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
323A12851E5F266B004975B8 /* CopyFiles */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "include/$(PRODUCT_NAME)";
|
||||
dstSubfolderSpec = 16;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
0CF68AC11AF0540F00FF9E5C /* libART.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libART.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0CF68ADB1AF0549300FF9E5C /* ARTCGFloatArray.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTCGFloatArray.h; sourceTree = "<group>"; };
|
||||
0CF68ADC1AF0549300FF9E5C /* ARTContainer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTContainer.h; sourceTree = "<group>"; };
|
||||
0CF68ADD1AF0549300FF9E5C /* ARTGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTGroup.h; sourceTree = "<group>"; };
|
||||
0CF68ADE1AF0549300FF9E5C /* ARTGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTGroup.m; sourceTree = "<group>"; };
|
||||
0CF68ADF1AF0549300FF9E5C /* ARTNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTNode.h; sourceTree = "<group>"; };
|
||||
0CF68AE01AF0549300FF9E5C /* ARTNode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTNode.m; sourceTree = "<group>"; };
|
||||
0CF68AE11AF0549300FF9E5C /* ARTRenderable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTRenderable.h; sourceTree = "<group>"; };
|
||||
0CF68AE21AF0549300FF9E5C /* ARTRenderable.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTRenderable.m; sourceTree = "<group>"; };
|
||||
0CF68AE31AF0549300FF9E5C /* ARTShape.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTShape.h; sourceTree = "<group>"; };
|
||||
0CF68AE41AF0549300FF9E5C /* ARTShape.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTShape.m; sourceTree = "<group>"; };
|
||||
0CF68AE51AF0549300FF9E5C /* ARTSurfaceView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTSurfaceView.h; sourceTree = "<group>"; };
|
||||
0CF68AE61AF0549300FF9E5C /* ARTSurfaceView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTSurfaceView.m; sourceTree = "<group>"; };
|
||||
0CF68AE71AF0549300FF9E5C /* ARTText.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTText.h; sourceTree = "<group>"; };
|
||||
0CF68AE81AF0549300FF9E5C /* ARTText.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTText.m; sourceTree = "<group>"; };
|
||||
0CF68AE91AF0549300FF9E5C /* ARTTextFrame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTTextFrame.h; sourceTree = "<group>"; };
|
||||
0CF68AEB1AF0549300FF9E5C /* ARTBrush.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTBrush.h; sourceTree = "<group>"; };
|
||||
0CF68AEC1AF0549300FF9E5C /* ARTBrush.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTBrush.m; sourceTree = "<group>"; };
|
||||
0CF68AED1AF0549300FF9E5C /* ARTLinearGradient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTLinearGradient.h; sourceTree = "<group>"; };
|
||||
0CF68AEE1AF0549300FF9E5C /* ARTLinearGradient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTLinearGradient.m; sourceTree = "<group>"; };
|
||||
0CF68AEF1AF0549300FF9E5C /* ARTPattern.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTPattern.h; sourceTree = "<group>"; };
|
||||
0CF68AF01AF0549300FF9E5C /* ARTPattern.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTPattern.m; sourceTree = "<group>"; };
|
||||
0CF68AF11AF0549300FF9E5C /* ARTRadialGradient.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTRadialGradient.h; sourceTree = "<group>"; };
|
||||
0CF68AF21AF0549300FF9E5C /* ARTRadialGradient.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTRadialGradient.m; sourceTree = "<group>"; };
|
||||
0CF68AF31AF0549300FF9E5C /* ARTSolidColor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTSolidColor.h; sourceTree = "<group>"; };
|
||||
0CF68AF41AF0549300FF9E5C /* ARTSolidColor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTSolidColor.m; sourceTree = "<group>"; };
|
||||
0CF68AF61AF0549300FF9E5C /* RCTConvert+ART.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "RCTConvert+ART.h"; sourceTree = "<group>"; };
|
||||
0CF68AF71AF0549300FF9E5C /* RCTConvert+ART.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "RCTConvert+ART.m"; sourceTree = "<group>"; };
|
||||
0CF68AF91AF0549300FF9E5C /* ARTGroupManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTGroupManager.h; sourceTree = "<group>"; };
|
||||
0CF68AFA1AF0549300FF9E5C /* ARTGroupManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTGroupManager.m; sourceTree = "<group>"; };
|
||||
0CF68AFB1AF0549300FF9E5C /* ARTNodeManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTNodeManager.h; sourceTree = "<group>"; };
|
||||
0CF68AFC1AF0549300FF9E5C /* ARTNodeManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTNodeManager.m; sourceTree = "<group>"; };
|
||||
0CF68AFD1AF0549300FF9E5C /* ARTRenderableManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTRenderableManager.h; sourceTree = "<group>"; };
|
||||
0CF68AFE1AF0549300FF9E5C /* ARTRenderableManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTRenderableManager.m; sourceTree = "<group>"; };
|
||||
0CF68AFF1AF0549300FF9E5C /* ARTShapeManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTShapeManager.h; sourceTree = "<group>"; };
|
||||
0CF68B001AF0549300FF9E5C /* ARTShapeManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTShapeManager.m; sourceTree = "<group>"; };
|
||||
0CF68B011AF0549300FF9E5C /* ARTSurfaceViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTSurfaceViewManager.h; sourceTree = "<group>"; };
|
||||
0CF68B021AF0549300FF9E5C /* ARTSurfaceViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTSurfaceViewManager.m; sourceTree = "<group>"; };
|
||||
0CF68B031AF0549300FF9E5C /* ARTTextManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ARTTextManager.h; sourceTree = "<group>"; };
|
||||
0CF68B041AF0549300FF9E5C /* ARTTextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ARTTextManager.m; sourceTree = "<group>"; };
|
||||
323A12871E5F266B004975B8 /* libART-tvOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libART-tvOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
0CF68ABE1AF0540F00FF9E5C /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
323A12841E5F266B004975B8 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
0CF68AB81AF0540F00FF9E5C = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0CF68AEA1AF0549300FF9E5C /* Brushes */,
|
||||
0CF68AF81AF0549300FF9E5C /* ViewManagers */,
|
||||
0CF68ADB1AF0549300FF9E5C /* ARTCGFloatArray.h */,
|
||||
0CF68ADC1AF0549300FF9E5C /* ARTContainer.h */,
|
||||
0CF68ADD1AF0549300FF9E5C /* ARTGroup.h */,
|
||||
0CF68ADE1AF0549300FF9E5C /* ARTGroup.m */,
|
||||
0CF68ADF1AF0549300FF9E5C /* ARTNode.h */,
|
||||
0CF68AE01AF0549300FF9E5C /* ARTNode.m */,
|
||||
0CF68AE11AF0549300FF9E5C /* ARTRenderable.h */,
|
||||
0CF68AE21AF0549300FF9E5C /* ARTRenderable.m */,
|
||||
0CF68AE31AF0549300FF9E5C /* ARTShape.h */,
|
||||
0CF68AE41AF0549300FF9E5C /* ARTShape.m */,
|
||||
0CF68AE51AF0549300FF9E5C /* ARTSurfaceView.h */,
|
||||
0CF68AE61AF0549300FF9E5C /* ARTSurfaceView.m */,
|
||||
0CF68AE71AF0549300FF9E5C /* ARTText.h */,
|
||||
0CF68AE81AF0549300FF9E5C /* ARTText.m */,
|
||||
0CF68AE91AF0549300FF9E5C /* ARTTextFrame.h */,
|
||||
0CF68AF61AF0549300FF9E5C /* RCTConvert+ART.h */,
|
||||
0CF68AF71AF0549300FF9E5C /* RCTConvert+ART.m */,
|
||||
0CF68AC21AF0540F00FF9E5C /* Products */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
0CF68AC21AF0540F00FF9E5C /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0CF68AC11AF0540F00FF9E5C /* libART.a */,
|
||||
323A12871E5F266B004975B8 /* libART-tvOS.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0CF68AEA1AF0549300FF9E5C /* Brushes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0CF68AEB1AF0549300FF9E5C /* ARTBrush.h */,
|
||||
0CF68AEC1AF0549300FF9E5C /* ARTBrush.m */,
|
||||
0CF68AED1AF0549300FF9E5C /* ARTLinearGradient.h */,
|
||||
0CF68AEE1AF0549300FF9E5C /* ARTLinearGradient.m */,
|
||||
0CF68AEF1AF0549300FF9E5C /* ARTPattern.h */,
|
||||
0CF68AF01AF0549300FF9E5C /* ARTPattern.m */,
|
||||
0CF68AF11AF0549300FF9E5C /* ARTRadialGradient.h */,
|
||||
0CF68AF21AF0549300FF9E5C /* ARTRadialGradient.m */,
|
||||
0CF68AF31AF0549300FF9E5C /* ARTSolidColor.h */,
|
||||
0CF68AF41AF0549300FF9E5C /* ARTSolidColor.m */,
|
||||
);
|
||||
path = Brushes;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0CF68AF81AF0549300FF9E5C /* ViewManagers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0CF68AF91AF0549300FF9E5C /* ARTGroupManager.h */,
|
||||
0CF68AFA1AF0549300FF9E5C /* ARTGroupManager.m */,
|
||||
0CF68AFB1AF0549300FF9E5C /* ARTNodeManager.h */,
|
||||
0CF68AFC1AF0549300FF9E5C /* ARTNodeManager.m */,
|
||||
0CF68AFD1AF0549300FF9E5C /* ARTRenderableManager.h */,
|
||||
0CF68AFE1AF0549300FF9E5C /* ARTRenderableManager.m */,
|
||||
0CF68AFF1AF0549300FF9E5C /* ARTShapeManager.h */,
|
||||
0CF68B001AF0549300FF9E5C /* ARTShapeManager.m */,
|
||||
0CF68B011AF0549300FF9E5C /* ARTSurfaceViewManager.h */,
|
||||
0CF68B021AF0549300FF9E5C /* ARTSurfaceViewManager.m */,
|
||||
0CF68B031AF0549300FF9E5C /* ARTTextManager.h */,
|
||||
0CF68B041AF0549300FF9E5C /* ARTTextManager.m */,
|
||||
);
|
||||
path = ViewManagers;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
0CF68AC01AF0540F00FF9E5C /* ART */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 0CF68AD51AF0540F00FF9E5C /* Build configuration list for PBXNativeTarget "ART" */;
|
||||
buildPhases = (
|
||||
0CF68ABD1AF0540F00FF9E5C /* Sources */,
|
||||
0CF68ABE1AF0540F00FF9E5C /* Frameworks */,
|
||||
0CF68ABF1AF0540F00FF9E5C /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = ART;
|
||||
productName = ART;
|
||||
productReference = 0CF68AC11AF0540F00FF9E5C /* libART.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
323A12861E5F266B004975B8 /* ART-tvOS */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 323A128D1E5F266B004975B8 /* Build configuration list for PBXNativeTarget "ART-tvOS" */;
|
||||
buildPhases = (
|
||||
323A12831E5F266B004975B8 /* Sources */,
|
||||
323A12841E5F266B004975B8 /* Frameworks */,
|
||||
323A12851E5F266B004975B8 /* CopyFiles */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = "ART-tvOS";
|
||||
productName = "ART-tvOS";
|
||||
productReference = 323A12871E5F266B004975B8 /* libART-tvOS.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
0CF68AB91AF0540F00FF9E5C /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0620;
|
||||
TargetAttributes = {
|
||||
0CF68AC01AF0540F00FF9E5C = {
|
||||
CreatedOnToolsVersion = 6.2;
|
||||
};
|
||||
323A12861E5F266B004975B8 = {
|
||||
CreatedOnToolsVersion = 6.2;
|
||||
ProvisioningStyle = Automatic;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 0CF68ABC1AF0540F00FF9E5C /* Build configuration list for PBXProject "ART" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = 0CF68AB81AF0540F00FF9E5C;
|
||||
productRefGroup = 0CF68AC21AF0540F00FF9E5C /* Products */;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
0CF68AC01AF0540F00FF9E5C /* ART */,
|
||||
323A12861E5F266B004975B8 /* ART-tvOS */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
0CF68ABD1AF0540F00FF9E5C /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
0CF68B161AF0549300FF9E5C /* ARTTextManager.m in Sources */,
|
||||
0CF68B111AF0549300FF9E5C /* ARTGroupManager.m in Sources */,
|
||||
0CF68B0D1AF0549300FF9E5C /* ARTPattern.m in Sources */,
|
||||
0CF68B0A1AF0549300FF9E5C /* ARTText.m in Sources */,
|
||||
0CF68B121AF0549300FF9E5C /* ARTNodeManager.m in Sources */,
|
||||
0CF68B051AF0549300FF9E5C /* ARTGroup.m in Sources */,
|
||||
0CF68B131AF0549300FF9E5C /* ARTRenderableManager.m in Sources */,
|
||||
0CF68B091AF0549300FF9E5C /* ARTSurfaceView.m in Sources */,
|
||||
0CF68B0E1AF0549300FF9E5C /* ARTRadialGradient.m in Sources */,
|
||||
0CF68B151AF0549300FF9E5C /* ARTSurfaceViewManager.m in Sources */,
|
||||
0CF68B081AF0549300FF9E5C /* ARTShape.m in Sources */,
|
||||
0CF68B071AF0549300FF9E5C /* ARTRenderable.m in Sources */,
|
||||
0CF68B101AF0549300FF9E5C /* RCTConvert+ART.m in Sources */,
|
||||
0CF68B061AF0549300FF9E5C /* ARTNode.m in Sources */,
|
||||
0CF68B0F1AF0549300FF9E5C /* ARTSolidColor.m in Sources */,
|
||||
0CF68B0C1AF0549300FF9E5C /* ARTLinearGradient.m in Sources */,
|
||||
0CF68B0B1AF0549300FF9E5C /* ARTBrush.m in Sources */,
|
||||
0CF68B141AF0549300FF9E5C /* ARTShapeManager.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
323A12831E5F266B004975B8 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
325CF7B71E5F2ABA00AC9606 /* ARTTextManager.m in Sources */,
|
||||
325CF7B21E5F2ABA00AC9606 /* ARTGroupManager.m in Sources */,
|
||||
325CF7AF1E5F2ABA00AC9606 /* ARTPattern.m in Sources */,
|
||||
325CF7BD1E5F2ABA00AC9606 /* ARTText.m in Sources */,
|
||||
325CF7B31E5F2ABA00AC9606 /* ARTNodeManager.m in Sources */,
|
||||
325CF7B81E5F2ABA00AC9606 /* ARTGroup.m in Sources */,
|
||||
325CF7B41E5F2ABA00AC9606 /* ARTRenderableManager.m in Sources */,
|
||||
325CF7BC1E5F2ABA00AC9606 /* ARTSurfaceView.m in Sources */,
|
||||
325CF7B01E5F2ABA00AC9606 /* ARTRadialGradient.m in Sources */,
|
||||
325CF7B61E5F2ABA00AC9606 /* ARTSurfaceViewManager.m in Sources */,
|
||||
325CF7BB1E5F2ABA00AC9606 /* ARTShape.m in Sources */,
|
||||
325CF7BA1E5F2ABA00AC9606 /* ARTRenderable.m in Sources */,
|
||||
325CF7BE1E5F2ABA00AC9606 /* RCTConvert+ART.m in Sources */,
|
||||
325CF7B91E5F2ABA00AC9606 /* ARTNode.m in Sources */,
|
||||
325CF7B11E5F2ABA00AC9606 /* ARTSolidColor.m in Sources */,
|
||||
325CF7AE1E5F2ABA00AC9606 /* ARTLinearGradient.m in Sources */,
|
||||
325CF7AD1E5F2ABA00AC9606 /* ARTBrush.m in Sources */,
|
||||
325CF7B51E5F2ABA00AC9606 /* ARTShapeManager.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin XCBuildConfiguration section */
|
||||
0CF68AD31AF0540F00FF9E5C /* 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;
|
||||
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_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
0CF68AD41AF0540F00FF9E5C /* 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 = NO;
|
||||
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_UNDECLARED_SELECTOR = YES;
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
0CF68AD61AF0540F00FF9E5C /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
0CF68AD71AF0540F00FF9E5C /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
323A128E1E5F266B004975B8 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = appletvos;
|
||||
SKIP_INSTALL = YES;
|
||||
TVOS_DEPLOYMENT_TARGET = 9.2;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
323A128F1E5F266B004975B8 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_ANALYZER_NONNULL = YES;
|
||||
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SDKROOT = appletvos;
|
||||
SKIP_INSTALL = YES;
|
||||
TVOS_DEPLOYMENT_TARGET = 9.2;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
0CF68ABC1AF0540F00FF9E5C /* Build configuration list for PBXProject "ART" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
0CF68AD31AF0540F00FF9E5C /* Debug */,
|
||||
0CF68AD41AF0540F00FF9E5C /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
0CF68AD51AF0540F00FF9E5C /* Build configuration list for PBXNativeTarget "ART" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
0CF68AD61AF0540F00FF9E5C /* Debug */,
|
||||
0CF68AD71AF0540F00FF9E5C /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
323A128D1E5F266B004975B8 /* Build configuration list for PBXNativeTarget "ART-tvOS" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
323A128E1E5F266B004975B8 /* Debug */,
|
||||
323A128F1E5F266B004975B8 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 0CF68AB91AF0540F00FF9E5C /* Project object */;
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// A little helper to make sure we have the right memory allocation ready for use.
|
||||
// We assume that we will only this in one place so no reference counting is necessary.
|
||||
// Needs to be freed when dealloced.
|
||||
|
||||
// This is fragile since this relies on these values not getting reused. Consider
|
||||
// wrapping these in an Obj-C class or some ARC hackery to get refcounting.
|
||||
|
||||
typedef struct {
|
||||
size_t count;
|
||||
CGFloat *array;
|
||||
} ARTCGFloatArray;
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
@protocol ARTContainer <NSObject>
|
||||
|
||||
// This is used as a hook for child to mark it's parent as dirty.
|
||||
// This bubbles up to the root which gets marked as dirty.
|
||||
- (void)invalidate;
|
||||
|
||||
@end
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
#import "ARTContainer.h"
|
||||
#import "ARTNode.h"
|
||||
|
||||
@interface ARTGroup : ARTNode <ARTContainer>
|
||||
|
||||
@property (nonatomic, assign) CGRect clipping;
|
||||
|
||||
@end
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTGroup.h"
|
||||
|
||||
@implementation ARTGroup
|
||||
|
||||
- (void)renderLayerTo:(CGContextRef)context
|
||||
{
|
||||
|
||||
if (!CGRectIsEmpty(self.clipping)) {
|
||||
CGContextClipToRect(context, self.clipping);
|
||||
}
|
||||
|
||||
for (ARTNode *node in self.subviews) {
|
||||
[node renderTo:context];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,32 +0,0 @@
|
||||
/**
|
||||
* 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 <React/UIView+React.h>
|
||||
|
||||
/**
|
||||
* ART nodes are implemented as empty UIViews but this is just an implementation detail to fit
|
||||
* into the existing view management. They should also be shadow views and painted on a background
|
||||
* thread.
|
||||
*/
|
||||
|
||||
@interface ARTNode : UIView
|
||||
|
||||
@property (nonatomic, assign) CGFloat opacity;
|
||||
|
||||
- (void)invalidate;
|
||||
- (void)renderTo:(CGContextRef)context;
|
||||
|
||||
/**
|
||||
* renderTo will take opacity into account and draw renderLayerTo off-screen if there is opacity
|
||||
* specified, then composite that onto the context. renderLayerTo always draws at opacity=1.
|
||||
* @abstract
|
||||
*/
|
||||
- (void)renderLayerTo:(CGContextRef)context;
|
||||
|
||||
@end
|
||||
@@ -1,82 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTNode.h"
|
||||
|
||||
#import "ARTContainer.h"
|
||||
|
||||
@implementation ARTNode
|
||||
|
||||
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
|
||||
{
|
||||
[super insertReactSubview:subview atIndex:atIndex];
|
||||
[self insertSubview:subview atIndex:atIndex];
|
||||
[self invalidate];
|
||||
}
|
||||
|
||||
- (void)removeReactSubview:(UIView *)subview
|
||||
{
|
||||
[super removeReactSubview:subview];
|
||||
[self invalidate];
|
||||
}
|
||||
|
||||
- (void)didUpdateReactSubviews
|
||||
{
|
||||
// Do nothing, as subviews are inserted by insertReactSubview:
|
||||
}
|
||||
|
||||
- (void)setOpacity:(CGFloat)opacity
|
||||
{
|
||||
[self invalidate];
|
||||
_opacity = opacity;
|
||||
}
|
||||
|
||||
- (void)setTransform:(CGAffineTransform)transform
|
||||
{
|
||||
[self invalidate];
|
||||
super.transform = transform;
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
id<ARTContainer> container = (id<ARTContainer>)self.superview;
|
||||
[container invalidate];
|
||||
}
|
||||
|
||||
- (void)renderTo:(CGContextRef)context
|
||||
{
|
||||
if (self.opacity <= 0) {
|
||||
// Nothing to paint
|
||||
return;
|
||||
}
|
||||
if (self.opacity >= 1) {
|
||||
// Just paint at full opacity
|
||||
CGContextSaveGState(context);
|
||||
CGContextConcatCTM(context, self.transform);
|
||||
CGContextSetAlpha(context, 1);
|
||||
[self renderLayerTo:context];
|
||||
CGContextRestoreGState(context);
|
||||
return;
|
||||
}
|
||||
// This needs to be painted on a layer before being composited.
|
||||
CGContextSaveGState(context);
|
||||
CGContextConcatCTM(context, self.transform);
|
||||
CGContextSetAlpha(context, self.opacity);
|
||||
CGContextBeginTransparencyLayer(context, NULL);
|
||||
[self renderLayerTo:context];
|
||||
CGContextEndTransparencyLayer(context);
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
- (void)renderLayerTo:(CGContextRef)context
|
||||
{
|
||||
// abstract
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
#import "ARTBrush.h"
|
||||
#import "ARTCGFloatArray.h"
|
||||
#import "ARTNode.h"
|
||||
|
||||
@interface ARTRenderable : ARTNode
|
||||
|
||||
@property (nonatomic, strong) ARTBrush *fill;
|
||||
@property (nonatomic, assign) CGColorRef stroke;
|
||||
@property (nonatomic, assign) CGFloat strokeWidth;
|
||||
@property (nonatomic, assign) CGLineCap strokeCap;
|
||||
@property (nonatomic, assign) CGLineJoin strokeJoin;
|
||||
@property (nonatomic, assign) ARTCGFloatArray strokeDash;
|
||||
|
||||
@end
|
||||
@@ -1,89 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTRenderable.h"
|
||||
|
||||
@implementation ARTRenderable
|
||||
|
||||
- (void)setFill:(ARTBrush *)fill
|
||||
{
|
||||
[self invalidate];
|
||||
_fill = fill;
|
||||
}
|
||||
|
||||
- (void)setStroke:(CGColorRef)stroke
|
||||
{
|
||||
if (stroke == _stroke) {
|
||||
return;
|
||||
}
|
||||
[self invalidate];
|
||||
CGColorRelease(_stroke);
|
||||
_stroke = CGColorRetain(stroke);
|
||||
}
|
||||
|
||||
- (void)setStrokeWidth:(CGFloat)strokeWidth
|
||||
{
|
||||
[self invalidate];
|
||||
_strokeWidth = strokeWidth;
|
||||
}
|
||||
|
||||
- (void)setStrokeCap:(CGLineCap)strokeCap
|
||||
{
|
||||
[self invalidate];
|
||||
_strokeCap = strokeCap;
|
||||
}
|
||||
|
||||
- (void)setStrokeJoin:(CGLineJoin)strokeJoin
|
||||
{
|
||||
[self invalidate];
|
||||
_strokeJoin = strokeJoin;
|
||||
}
|
||||
|
||||
- (void)setStrokeDash:(ARTCGFloatArray)strokeDash
|
||||
{
|
||||
if (strokeDash.array == _strokeDash.array) {
|
||||
return;
|
||||
}
|
||||
if (_strokeDash.array) {
|
||||
free(_strokeDash.array);
|
||||
}
|
||||
[self invalidate];
|
||||
_strokeDash = strokeDash;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGColorRelease(_stroke);
|
||||
if (_strokeDash.array) {
|
||||
free(_strokeDash.array);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)renderTo:(CGContextRef)context
|
||||
{
|
||||
if (self.opacity <= 0 || self.opacity >= 1 || (self.fill && self.stroke)) {
|
||||
// If we have both fill and stroke, we will need to paint this using normal compositing
|
||||
[super renderTo: context];
|
||||
return;
|
||||
}
|
||||
// This is a terminal with only one painting. Therefore we don't need to paint this
|
||||
// off-screen. We can just composite it straight onto the buffer.
|
||||
CGContextSaveGState(context);
|
||||
CGContextConcatCTM(context, self.transform);
|
||||
CGContextSetAlpha(context, self.opacity);
|
||||
[self renderLayerTo:context];
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
- (void)renderLayerTo:(CGContextRef)context
|
||||
{
|
||||
// abstract
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,76 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule ARTSerializablePath
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
// TODO: Move this into an ART mode called "serialized" or something
|
||||
|
||||
var Class = require('art/core/class.js');
|
||||
var Path = require('art/core/path.js');
|
||||
|
||||
var MOVE_TO = 0;
|
||||
var CLOSE = 1;
|
||||
var LINE_TO = 2;
|
||||
var CURVE_TO = 3;
|
||||
var ARC = 4;
|
||||
|
||||
var SerializablePath = Class(Path, {
|
||||
|
||||
initialize: function(path) {
|
||||
this.reset();
|
||||
if (path instanceof SerializablePath) {
|
||||
this.path = path.path.slice(0);
|
||||
} else if (path) {
|
||||
if (path.applyToPath) {
|
||||
path.applyToPath(this);
|
||||
} else {
|
||||
this.push(path);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
onReset: function() {
|
||||
this.path = [];
|
||||
},
|
||||
|
||||
onMove: function(sx, sy, x, y) {
|
||||
this.path.push(MOVE_TO, x, y);
|
||||
},
|
||||
|
||||
onLine: function(sx, sy, x, y) {
|
||||
this.path.push(LINE_TO, x, y);
|
||||
},
|
||||
|
||||
onBezierCurve: function(sx, sy, p1x, p1y, p2x, p2y, x, y) {
|
||||
this.path.push(CURVE_TO, p1x, p1y, p2x, p2y, x, y);
|
||||
},
|
||||
|
||||
_arcToBezier: Path.prototype.onArc,
|
||||
|
||||
onArc: function(sx, sy, ex, ey, cx, cy, rx, ry, sa, ea, ccw, rotation) {
|
||||
if (rx !== ry || rotation) {
|
||||
return this._arcToBezier(
|
||||
sx, sy, ex, ey, cx, cy, rx, ry, sa, ea, ccw, rotation
|
||||
);
|
||||
}
|
||||
this.path.push(ARC, cx, cy, rx, sa, ea, ccw ? 0 : 1);
|
||||
},
|
||||
|
||||
onClose: function() {
|
||||
this.path.push(CLOSE);
|
||||
},
|
||||
|
||||
toJSON: function() {
|
||||
return this.path;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
module.exports = SerializablePath;
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
#import "ARTRenderable.h"
|
||||
|
||||
@interface ARTShape : ARTRenderable
|
||||
|
||||
@property (nonatomic, assign) CGPathRef d;
|
||||
|
||||
@end
|
||||
@@ -1,68 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTShape.h"
|
||||
|
||||
@implementation ARTShape
|
||||
|
||||
- (void)setD:(CGPathRef)d
|
||||
{
|
||||
if (d == _d) {
|
||||
return;
|
||||
}
|
||||
[self invalidate];
|
||||
CGPathRelease(_d);
|
||||
_d = CGPathRetain(d);
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGPathRelease(_d);
|
||||
}
|
||||
|
||||
- (void)renderLayerTo:(CGContextRef)context
|
||||
{
|
||||
if ((!self.fill && !self.stroke) || !self.d) {
|
||||
return;
|
||||
}
|
||||
|
||||
CGPathDrawingMode mode = kCGPathStroke;
|
||||
if (self.fill) {
|
||||
if ([self.fill applyFillColor:context]) {
|
||||
mode = kCGPathFill;
|
||||
} else {
|
||||
CGContextSaveGState(context);
|
||||
CGContextAddPath(context, self.d);
|
||||
CGContextClip(context);
|
||||
[self.fill paint:context];
|
||||
CGContextRestoreGState(context);
|
||||
if (!self.stroke) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (self.stroke) {
|
||||
CGContextSetStrokeColorWithColor(context, self.stroke);
|
||||
CGContextSetLineWidth(context, self.strokeWidth);
|
||||
CGContextSetLineCap(context, self.strokeCap);
|
||||
CGContextSetLineJoin(context, self.strokeJoin);
|
||||
ARTCGFloatArray dash = self.strokeDash;
|
||||
if (dash.count) {
|
||||
CGContextSetLineDash(context, 0, dash.array, dash.count);
|
||||
}
|
||||
if (mode == kCGPathFill) {
|
||||
mode = kCGPathFillStroke;
|
||||
}
|
||||
}
|
||||
|
||||
CGContextAddPath(context, self.d);
|
||||
CGContextDrawPath(context, mode);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* 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 <UIKit/UIKit.h>
|
||||
|
||||
#import "ARTContainer.h"
|
||||
|
||||
@interface ARTSurfaceView : UIView <ARTContainer>
|
||||
|
||||
@end
|
||||
@@ -1,54 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTSurfaceView.h"
|
||||
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import "ARTNode.h"
|
||||
|
||||
@implementation ARTSurfaceView
|
||||
|
||||
- (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex
|
||||
{
|
||||
[super insertReactSubview:subview atIndex:atIndex];
|
||||
[self insertSubview:subview atIndex:atIndex];
|
||||
[self invalidate];
|
||||
}
|
||||
|
||||
- (void)removeReactSubview:(UIView *)subview
|
||||
{
|
||||
[super removeReactSubview:subview];
|
||||
[self invalidate];
|
||||
}
|
||||
|
||||
- (void)didUpdateReactSubviews
|
||||
{
|
||||
// Do nothing, as subviews are inserted by insertReactSubview:
|
||||
}
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
for (ARTNode *node in self.subviews) {
|
||||
[node renderTo:context];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)reactSetInheritedBackgroundColor:(UIColor *)inheritedBackgroundColor
|
||||
{
|
||||
self.backgroundColor = inheritedBackgroundColor;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* 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 <Foundation/Foundation.h>
|
||||
|
||||
#import "ARTRenderable.h"
|
||||
#import "ARTTextFrame.h"
|
||||
|
||||
@interface ARTText : ARTRenderable
|
||||
|
||||
@property (nonatomic, assign) CTTextAlignment alignment;
|
||||
@property (nonatomic, assign) ARTTextFrame textFrame;
|
||||
|
||||
@end
|
||||
@@ -1,127 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTText.h"
|
||||
|
||||
#import <CoreText/CoreText.h>
|
||||
|
||||
@implementation ARTText
|
||||
|
||||
- (void)setAlignment:(CTTextAlignment)alignment
|
||||
{
|
||||
[self invalidate];
|
||||
_alignment = alignment;
|
||||
}
|
||||
|
||||
static void ARTFreeTextFrame(ARTTextFrame frame)
|
||||
{
|
||||
if (frame.count) {
|
||||
// We must release each line before freeing up this struct
|
||||
for (int i = 0; i < frame.count; i++) {
|
||||
CFRelease(frame.lines[i]);
|
||||
}
|
||||
free(frame.lines);
|
||||
free(frame.widths);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setTextFrame:(ARTTextFrame)frame
|
||||
{
|
||||
if (frame.lines != _textFrame.lines) {
|
||||
ARTFreeTextFrame(_textFrame);
|
||||
}
|
||||
[self invalidate];
|
||||
_textFrame = frame;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
ARTFreeTextFrame(_textFrame);
|
||||
}
|
||||
|
||||
- (void)renderLayerTo:(CGContextRef)context
|
||||
{
|
||||
ARTTextFrame frame = self.textFrame;
|
||||
|
||||
if ((!self.fill && !self.stroke) || !frame.count) {
|
||||
return;
|
||||
}
|
||||
|
||||
// to-do: draw along a path
|
||||
|
||||
CGTextDrawingMode mode = kCGTextStroke;
|
||||
if (self.fill) {
|
||||
if ([self.fill applyFillColor:context]) {
|
||||
mode = kCGTextFill;
|
||||
} else {
|
||||
|
||||
for (int i = 0; i < frame.count; i++) {
|
||||
CGContextSaveGState(context);
|
||||
// Inverse the coordinate space since CoreText assumes a bottom-up coordinate space
|
||||
CGContextScaleCTM(context, 1.0, -1.0);
|
||||
CGContextSetTextDrawingMode(context, kCGTextClip);
|
||||
[self renderLineTo:context atIndex:i];
|
||||
// Inverse the coordinate space back to the original before filling
|
||||
CGContextScaleCTM(context, 1.0, -1.0);
|
||||
[self.fill paint:context];
|
||||
// Restore the state so that the next line can be clipped separately
|
||||
CGContextRestoreGState(context);
|
||||
}
|
||||
|
||||
if (!self.stroke) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (self.stroke) {
|
||||
CGContextSetStrokeColorWithColor(context, self.stroke);
|
||||
CGContextSetLineWidth(context, self.strokeWidth);
|
||||
CGContextSetLineCap(context, self.strokeCap);
|
||||
CGContextSetLineJoin(context, self.strokeJoin);
|
||||
ARTCGFloatArray dash = self.strokeDash;
|
||||
if (dash.count) {
|
||||
CGContextSetLineDash(context, 0, dash.array, dash.count);
|
||||
}
|
||||
if (mode == kCGTextFill) {
|
||||
mode = kCGTextFillStroke;
|
||||
}
|
||||
}
|
||||
|
||||
CGContextSetTextDrawingMode(context, mode);
|
||||
|
||||
// Inverse the coordinate space since CoreText assumes a bottom-up coordinate space
|
||||
CGContextScaleCTM(context, 1.0, -1.0);
|
||||
for (int i = 0; i < frame.count; i++) {
|
||||
[self renderLineTo:context atIndex:i];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)renderLineTo:(CGContextRef)context atIndex:(int)index
|
||||
{
|
||||
ARTTextFrame frame = self.textFrame;
|
||||
CGFloat shift;
|
||||
switch (self.alignment) {
|
||||
case kCTTextAlignmentRight:
|
||||
shift = frame.widths[index];
|
||||
break;
|
||||
case kCTTextAlignmentCenter:
|
||||
shift = (frame.widths[index] / 2);
|
||||
break;
|
||||
default:
|
||||
shift = 0;
|
||||
break;
|
||||
}
|
||||
// We should consider snapping this shift to device pixels to improve rendering quality
|
||||
// when a line has subpixel width.
|
||||
CGContextSetTextPosition(context, -shift, -frame.baseLine - frame.lineHeight * index);
|
||||
CTLineRef line = frame.lines[index];
|
||||
CTLineDraw(line, context);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* 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 <CoreText/CoreText.h>
|
||||
|
||||
// A little helper to make sure we have a set of lines including width ready for use.
|
||||
// We assume that we will only this in one place so no reference counting is necessary.
|
||||
// Needs to be freed when dealloced.
|
||||
|
||||
// This is fragile since this relies on these values not getting reused. Consider
|
||||
// wrapping these in an Obj-C class or some ARC hackery to get refcounting.
|
||||
|
||||
typedef struct {
|
||||
size_t count;
|
||||
CGFloat baseLine; // Distance from the origin to the base line of the first line
|
||||
CGFloat lineHeight; // Distance between lines
|
||||
CTLineRef *lines;
|
||||
CGFloat *widths; // Width of each line
|
||||
} ARTTextFrame;
|
||||
@@ -1,35 +0,0 @@
|
||||
/**
|
||||
* 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 <CoreGraphics/CoreGraphics.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@interface ARTBrush : NSObject
|
||||
|
||||
/* @abstract */
|
||||
- (instancetype)initWithArray:(NSArray *)data NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
* For certain brushes we can fast path a combined fill and stroke.
|
||||
* For those brushes we override applyFillColor which sets the fill
|
||||
* color to be used by those batch paints. Those return YES.
|
||||
* We can't batch gradient painting in CoreGraphics, so those will
|
||||
* return NO and paint gets called instead.
|
||||
* @abstract
|
||||
*/
|
||||
- (BOOL)applyFillColor:(CGContextRef)context;
|
||||
|
||||
/**
|
||||
* paint fills the context with a brush. The context is assumed to
|
||||
* be clipped.
|
||||
* @abstract
|
||||
*/
|
||||
- (void)paint:(CGContextRef)context;
|
||||
|
||||
@end
|
||||
@@ -1,33 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTBrush.h"
|
||||
|
||||
#import <React/RCTDefines.h>
|
||||
|
||||
@implementation ARTBrush
|
||||
|
||||
- (instancetype)initWithArray:(NSArray *)data
|
||||
{
|
||||
return [super init];
|
||||
}
|
||||
|
||||
RCT_NOT_IMPLEMENTED(- (instancetype)init)
|
||||
|
||||
- (BOOL)applyFillColor:(CGContextRef)context
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)paint:(CGContextRef)context
|
||||
{
|
||||
// abstract
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTBrush.h"
|
||||
|
||||
@interface ARTLinearGradient : ARTBrush
|
||||
|
||||
@end
|
||||
@@ -1,50 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTLinearGradient.h"
|
||||
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTLinearGradient
|
||||
{
|
||||
CGGradientRef _gradient;
|
||||
CGPoint _startPoint;
|
||||
CGPoint _endPoint;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray<NSNumber *> *)array
|
||||
{
|
||||
if ((self = [super initWithArray:array])) {
|
||||
if (array.count < 5) {
|
||||
RCTLogError(@"-[%@ %@] expects 5 elements, received %@",
|
||||
self.class, NSStringFromSelector(_cmd), array);
|
||||
return nil;
|
||||
}
|
||||
_startPoint = [RCTConvert CGPoint:array offset:1];
|
||||
_endPoint = [RCTConvert CGPoint:array offset:3];
|
||||
_gradient = CGGradientRetain([RCTConvert CGGradient:array offset:5]);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGGradientRelease(_gradient);
|
||||
}
|
||||
|
||||
- (void)paint:(CGContextRef)context
|
||||
{
|
||||
CGGradientDrawingOptions extendOptions =
|
||||
kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation;
|
||||
CGContextDrawLinearGradient(context, _gradient, _startPoint, _endPoint, extendOptions);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTBrush.h"
|
||||
|
||||
@interface ARTPattern : ARTBrush
|
||||
|
||||
@end
|
||||
@@ -1,51 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTPattern.h"
|
||||
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTPattern
|
||||
{
|
||||
CGImageRef _image;
|
||||
CGRect _rect;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray<id /* imagesource + numbers */> *)array
|
||||
{
|
||||
if ((self = [super initWithArray:array])) {
|
||||
if (array.count < 6) {
|
||||
RCTLogError(@"-[%@ %@] expects 6 elements, received %@",
|
||||
self.class, NSStringFromSelector(_cmd), array);
|
||||
return nil;
|
||||
}
|
||||
_image = CGImageRetain([RCTConvert CGImage:array[1]]);
|
||||
_rect = [RCTConvert CGRect:array offset:2];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGImageRelease(_image);
|
||||
}
|
||||
|
||||
// Note: This could use applyFillColor with a pattern. This could be more efficient but
|
||||
// to do that, we need to calculate our own user space CTM.
|
||||
|
||||
- (void)paint:(CGContextRef)context
|
||||
{
|
||||
CGContextDrawTiledImage(context, _rect, _image);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@end
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTBrush.h"
|
||||
|
||||
@interface ARTRadialGradient : ARTBrush
|
||||
|
||||
@end
|
||||
@@ -1,57 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTRadialGradient.h"
|
||||
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTRadialGradient
|
||||
{
|
||||
CGGradientRef _gradient;
|
||||
CGPoint _focusPoint;
|
||||
CGPoint _centerPoint;
|
||||
CGFloat _radius;
|
||||
CGFloat _radiusRatio;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray<NSNumber *> *)array
|
||||
{
|
||||
if ((self = [super initWithArray:array])) {
|
||||
if (array.count < 7) {
|
||||
RCTLogError(@"-[%@ %@] expects 7 elements, received %@",
|
||||
self.class, NSStringFromSelector(_cmd), array);
|
||||
return nil;
|
||||
}
|
||||
_radius = [RCTConvert CGFloat:array[3]];
|
||||
_radiusRatio = [RCTConvert CGFloat:array[4]] / _radius;
|
||||
_focusPoint.x = [RCTConvert CGFloat:array[1]];
|
||||
_focusPoint.y = [RCTConvert CGFloat:array[2]] / _radiusRatio;
|
||||
_centerPoint.x = [RCTConvert CGFloat:array[5]];
|
||||
_centerPoint.y = [RCTConvert CGFloat:array[6]] / _radiusRatio;
|
||||
_gradient = CGGradientRetain([RCTConvert CGGradient:array offset:7]);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGGradientRelease(_gradient);
|
||||
}
|
||||
|
||||
- (void)paint:(CGContextRef)context
|
||||
{
|
||||
CGAffineTransform transform = CGAffineTransformMakeScale(1, _radiusRatio);
|
||||
CGContextConcatCTM(context, transform);
|
||||
CGGradientDrawingOptions extendOptions = kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation;
|
||||
CGContextDrawRadialGradient(context, _gradient, _focusPoint, 0, _centerPoint, _radius, extendOptions);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTBrush.h"
|
||||
|
||||
@interface ARTSolidColor : ARTBrush
|
||||
|
||||
@end
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTSolidColor.h"
|
||||
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTSolidColor
|
||||
{
|
||||
CGColorRef _color;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray<NSNumber *> *)array
|
||||
{
|
||||
if ((self = [super initWithArray:array])) {
|
||||
_color = CGColorRetain([RCTConvert CGColor:array offset:1]);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
CGColorRelease(_color);
|
||||
}
|
||||
|
||||
- (BOOL)applyFillColor:(CGContextRef)context
|
||||
{
|
||||
CGContextSetFillColorWithColor(context, _color);
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* 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 <QuartzCore/QuartzCore.h>
|
||||
|
||||
#import <React/RCTConvert.h>
|
||||
|
||||
#import "ARTBrush.h"
|
||||
#import "ARTCGFloatArray.h"
|
||||
#import "ARTTextFrame.h"
|
||||
|
||||
@interface RCTConvert (ART)
|
||||
|
||||
+ (CGPathRef)CGPath:(id)json;
|
||||
+ (CTTextAlignment)CTTextAlignment:(id)json;
|
||||
+ (ARTTextFrame)ARTTextFrame:(id)json;
|
||||
+ (ARTCGFloatArray)ARTCGFloatArray:(id)json;
|
||||
+ (ARTBrush *)ARTBrush:(id)json;
|
||||
|
||||
+ (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset;
|
||||
+ (CGRect)CGRect:(id)json offset:(NSUInteger)offset;
|
||||
+ (CGColorRef)CGColor:(id)json offset:(NSUInteger)offset;
|
||||
+ (CGGradientRef)CGGradient:(id)json offset:(NSUInteger)offset;
|
||||
|
||||
@end
|
||||
@@ -1,224 +0,0 @@
|
||||
/**
|
||||
* 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 "RCTConvert+ART.h"
|
||||
|
||||
#import <React/RCTFont.h>
|
||||
#import <React/RCTLog.h>
|
||||
|
||||
#import "ARTLinearGradient.h"
|
||||
#import "ARTPattern.h"
|
||||
#import "ARTRadialGradient.h"
|
||||
#import "ARTSolidColor.h"
|
||||
|
||||
@implementation RCTConvert (ART)
|
||||
|
||||
+ (CGPathRef)CGPath:(id)json
|
||||
{
|
||||
NSArray *arr = [self NSNumberArray:json];
|
||||
|
||||
NSUInteger count = [arr count];
|
||||
|
||||
#define NEXT_VALUE [self double:arr[i++]]
|
||||
|
||||
CGMutablePathRef path = CGPathCreateMutable();
|
||||
CGPathMoveToPoint(path, NULL, 0, 0);
|
||||
|
||||
@try {
|
||||
NSUInteger i = 0;
|
||||
while (i < count) {
|
||||
NSUInteger type = [arr[i++] unsignedIntegerValue];
|
||||
switch (type) {
|
||||
case 0:
|
||||
CGPathMoveToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE);
|
||||
break;
|
||||
case 1:
|
||||
CGPathCloseSubpath(path);
|
||||
break;
|
||||
case 2:
|
||||
CGPathAddLineToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE);
|
||||
break;
|
||||
case 3:
|
||||
CGPathAddCurveToPoint(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE);
|
||||
break;
|
||||
case 4:
|
||||
CGPathAddArc(path, NULL, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE, NEXT_VALUE == 0);
|
||||
break;
|
||||
default:
|
||||
RCTLogError(@"Invalid CGPath type %zd at element %zd of %@", type, i, arr);
|
||||
CGPathRelease(path);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
RCTLogError(@"Invalid CGPath format: %@", arr);
|
||||
CGPathRelease(path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return (CGPathRef)CFAutorelease(path);
|
||||
}
|
||||
|
||||
RCT_ENUM_CONVERTER(CTTextAlignment, (@{
|
||||
@"auto": @(kCTTextAlignmentNatural),
|
||||
@"left": @(kCTTextAlignmentLeft),
|
||||
@"center": @(kCTTextAlignmentCenter),
|
||||
@"right": @(kCTTextAlignmentRight),
|
||||
@"justify": @(kCTTextAlignmentJustified),
|
||||
}), kCTTextAlignmentNatural, integerValue)
|
||||
|
||||
// This takes a tuple of text lines and a font to generate a CTLine for each text line.
|
||||
// This prepares everything for rendering a frame of text in ARTText.
|
||||
+ (ARTTextFrame)ARTTextFrame:(id)json
|
||||
{
|
||||
NSDictionary *dict = [self NSDictionary:json];
|
||||
ARTTextFrame frame;
|
||||
frame.count = 0;
|
||||
|
||||
NSArray *lines = [self NSArray:dict[@"lines"]];
|
||||
NSUInteger lineCount = [lines count];
|
||||
if (lineCount == 0) {
|
||||
return frame;
|
||||
}
|
||||
|
||||
CTFontRef font = (__bridge CTFontRef)[self UIFont:dict[@"font"]];
|
||||
if (!font) {
|
||||
return frame;
|
||||
}
|
||||
|
||||
// Create a dictionary for this font
|
||||
CFDictionaryRef attributes = (__bridge CFDictionaryRef)@{
|
||||
(NSString *)kCTFontAttributeName:(__bridge id)font,
|
||||
(NSString *)kCTForegroundColorFromContextAttributeName: @YES
|
||||
};
|
||||
|
||||
// Set up text frame with font metrics
|
||||
CGFloat size = CTFontGetSize(font);
|
||||
frame.count = lineCount;
|
||||
frame.baseLine = size; // estimate base line
|
||||
frame.lineHeight = size * 1.1; // Base on ART canvas line height estimate
|
||||
frame.lines = malloc(sizeof(CTLineRef) * lineCount);
|
||||
frame.widths = malloc(sizeof(CGFloat) * lineCount);
|
||||
|
||||
[lines enumerateObjectsUsingBlock:^(NSString *text, NSUInteger i, BOOL *stop) {
|
||||
|
||||
CFStringRef string = (__bridge CFStringRef)text;
|
||||
CFAttributedStringRef attrString = CFAttributedStringCreate(kCFAllocatorDefault, string, attributes);
|
||||
CTLineRef line = CTLineCreateWithAttributedString(attrString);
|
||||
CFRelease(attrString);
|
||||
|
||||
frame.lines[i] = line;
|
||||
frame.widths[i] = CTLineGetTypographicBounds(line, NULL, NULL, NULL);
|
||||
}];
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
+ (ARTCGFloatArray)ARTCGFloatArray:(id)json
|
||||
{
|
||||
NSArray *arr = [self NSNumberArray:json];
|
||||
NSUInteger count = arr.count;
|
||||
|
||||
ARTCGFloatArray array;
|
||||
array.count = count;
|
||||
array.array = NULL;
|
||||
|
||||
if (count) {
|
||||
// Ideally, these arrays should already use the same memory layout.
|
||||
// In that case we shouldn't need this new malloc.
|
||||
array.array = malloc(sizeof(CGFloat) * count);
|
||||
for (NSUInteger i = 0; i < count; i++) {
|
||||
array.array[i] = [arr[i] doubleValue];
|
||||
}
|
||||
}
|
||||
|
||||
return array;
|
||||
}
|
||||
|
||||
+ (ARTBrush *)ARTBrush:(id)json
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
NSUInteger type = [self NSUInteger:arr.firstObject];
|
||||
switch (type) {
|
||||
case 0: // solid color
|
||||
// These are probably expensive allocations since it's often the same value.
|
||||
// We should memoize colors but look ups may be just as expensive.
|
||||
return [[ARTSolidColor alloc] initWithArray:arr];
|
||||
case 1: // linear gradient
|
||||
return [[ARTLinearGradient alloc] initWithArray:arr];
|
||||
case 2: // radial gradient
|
||||
return [[ARTRadialGradient alloc] initWithArray:arr];
|
||||
case 3: // pattern
|
||||
return [[ARTPattern alloc] initWithArray:arr];
|
||||
default:
|
||||
RCTLogError(@"Unknown brush type: %zd", type);
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
+ (CGPoint)CGPoint:(id)json offset:(NSUInteger)offset
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
if (arr.count < offset + 2) {
|
||||
RCTLogError(@"Too few elements in array (expected at least %zd): %@", 2 + offset, arr);
|
||||
return CGPointZero;
|
||||
}
|
||||
return (CGPoint){
|
||||
[self CGFloat:arr[offset]],
|
||||
[self CGFloat:arr[offset + 1]],
|
||||
};
|
||||
}
|
||||
|
||||
+ (CGRect)CGRect:(id)json offset:(NSUInteger)offset
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
if (arr.count < offset + 4) {
|
||||
RCTLogError(@"Too few elements in array (expected at least %zd): %@", 4 + offset, arr);
|
||||
return CGRectZero;
|
||||
}
|
||||
return (CGRect){
|
||||
{[self CGFloat:arr[offset]], [self CGFloat:arr[offset + 1]]},
|
||||
{[self CGFloat:arr[offset + 2]], [self CGFloat:arr[offset + 3]]},
|
||||
};
|
||||
}
|
||||
|
||||
+ (CGColorRef)CGColor:(id)json offset:(NSUInteger)offset
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
if (arr.count < offset + 4) {
|
||||
RCTLogError(@"Too few elements in array (expected at least %zd): %@", 4 + offset, arr);
|
||||
return NULL;
|
||||
}
|
||||
return [self CGColor:[arr subarrayWithRange:(NSRange){offset, 4}]];
|
||||
}
|
||||
|
||||
+ (CGGradientRef)CGGradient:(id)json offset:(NSUInteger)offset
|
||||
{
|
||||
NSArray *arr = [self NSArray:json];
|
||||
if (arr.count < offset) {
|
||||
RCTLogError(@"Too few elements in array (expected at least %zd): %@", offset, arr);
|
||||
return NULL;
|
||||
}
|
||||
arr = [arr subarrayWithRange:(NSRange){offset, arr.count - offset}];
|
||||
ARTCGFloatArray colorsAndOffsets = [self ARTCGFloatArray:arr];
|
||||
size_t stops = colorsAndOffsets.count / 5;
|
||||
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
|
||||
CGGradientRef gradient = CGGradientCreateWithColorComponents(
|
||||
rgb,
|
||||
colorsAndOffsets.array,
|
||||
colorsAndOffsets.array + stops * 4,
|
||||
stops
|
||||
);
|
||||
CGColorSpaceRelease(rgb);
|
||||
free(colorsAndOffsets.array);
|
||||
return (CGGradientRef)CFAutorelease(gradient);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,586 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule ReactNativeART
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var Color = require('art/core/color');
|
||||
var Path = require('ARTSerializablePath');
|
||||
var Transform = require('art/core/transform');
|
||||
|
||||
var React = require('React');
|
||||
var PropTypes = require('prop-types');
|
||||
var ReactNativeViewAttributes = require('ReactNativeViewAttributes');
|
||||
|
||||
var createReactNativeComponentClass = require('createReactNativeComponentClass');
|
||||
var merge = require('merge');
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
|
||||
// Diff Helpers
|
||||
|
||||
function arrayDiffer(a, b) {
|
||||
if (a == null || b == null) {
|
||||
return true;
|
||||
}
|
||||
if (a.length !== b.length) {
|
||||
return true;
|
||||
}
|
||||
for (var i = 0; i < a.length; i++) {
|
||||
if (a[i] !== b[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function fontAndLinesDiffer(a, b) {
|
||||
if (a === b) {
|
||||
return false;
|
||||
}
|
||||
if (a.font !== b.font) {
|
||||
if (a.font === null) {
|
||||
return true;
|
||||
}
|
||||
if (b.font === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
a.font.fontFamily !== b.font.fontFamily ||
|
||||
a.font.fontSize !== b.font.fontSize ||
|
||||
a.font.fontWeight !== b.font.fontWeight ||
|
||||
a.font.fontStyle !== b.font.fontStyle
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return arrayDiffer(a.lines, b.lines);
|
||||
}
|
||||
|
||||
// Native Attributes
|
||||
|
||||
var SurfaceViewAttributes = merge(ReactNativeViewAttributes.UIView, {
|
||||
// This should contain pixel information such as width, height and
|
||||
// resolution to know what kind of buffer needs to be allocated.
|
||||
// Currently we rely on UIViews and style to figure that out.
|
||||
});
|
||||
|
||||
var NodeAttributes = {
|
||||
transform: { diff: arrayDiffer },
|
||||
opacity: true,
|
||||
};
|
||||
|
||||
var GroupAttributes = merge(NodeAttributes, {
|
||||
clipping: { diff: arrayDiffer }
|
||||
});
|
||||
|
||||
var RenderableAttributes = merge(NodeAttributes, {
|
||||
fill: { diff: arrayDiffer },
|
||||
stroke: { diff: arrayDiffer },
|
||||
strokeWidth: true,
|
||||
strokeCap: true,
|
||||
strokeJoin: true,
|
||||
strokeDash: { diff: arrayDiffer },
|
||||
});
|
||||
|
||||
var ShapeAttributes = merge(RenderableAttributes, {
|
||||
d: { diff: arrayDiffer },
|
||||
});
|
||||
|
||||
var TextAttributes = merge(RenderableAttributes, {
|
||||
alignment: true,
|
||||
frame: { diff: fontAndLinesDiffer },
|
||||
path: { diff: arrayDiffer }
|
||||
});
|
||||
|
||||
// Native Components
|
||||
|
||||
var NativeSurfaceView = createReactNativeComponentClass({
|
||||
validAttributes: SurfaceViewAttributes,
|
||||
uiViewClassName: 'ARTSurfaceView',
|
||||
});
|
||||
|
||||
var NativeGroup = createReactNativeComponentClass({
|
||||
validAttributes: GroupAttributes,
|
||||
uiViewClassName: 'ARTGroup',
|
||||
});
|
||||
|
||||
var NativeShape = createReactNativeComponentClass({
|
||||
validAttributes: ShapeAttributes,
|
||||
uiViewClassName: 'ARTShape',
|
||||
});
|
||||
|
||||
var NativeText = createReactNativeComponentClass({
|
||||
validAttributes: TextAttributes,
|
||||
uiViewClassName: 'ARTText',
|
||||
});
|
||||
|
||||
// Utilities
|
||||
|
||||
function childrenAsString(children) {
|
||||
if (!children) {
|
||||
return '';
|
||||
}
|
||||
if (typeof children === 'string') {
|
||||
return children;
|
||||
}
|
||||
if (children.length) {
|
||||
return children.join('\n');
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Surface - Root node of all ART
|
||||
|
||||
class Surface extends React.Component {
|
||||
static childContextTypes = {
|
||||
isInSurface: PropTypes.bool,
|
||||
};
|
||||
|
||||
getChildContext() {
|
||||
return { isInSurface: true };
|
||||
}
|
||||
|
||||
render() {
|
||||
var props = this.props;
|
||||
var w = extractNumber(props.width, 0);
|
||||
var h = extractNumber(props.height, 0);
|
||||
return (
|
||||
<NativeSurfaceView style={[props.style, { width: w, height: h }]}>
|
||||
{this.props.children}
|
||||
</NativeSurfaceView>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Node Props
|
||||
|
||||
// TODO: The desktop version of ART has title and cursor. We should have
|
||||
// accessibility support here too even though hovering doesn't work.
|
||||
|
||||
function extractNumber(value, defaultValue) {
|
||||
if (value == null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return +value;
|
||||
}
|
||||
|
||||
var pooledTransform = new Transform();
|
||||
|
||||
function extractTransform(props) {
|
||||
var scaleX = props.scaleX != null ? props.scaleX :
|
||||
props.scale != null ? props.scale : 1;
|
||||
var scaleY = props.scaleY != null ? props.scaleY :
|
||||
props.scale != null ? props.scale : 1;
|
||||
|
||||
pooledTransform
|
||||
.transformTo(1, 0, 0, 1, 0, 0)
|
||||
.move(props.x || 0, props.y || 0)
|
||||
.rotate(props.rotation || 0, props.originX, props.originY)
|
||||
.scale(scaleX, scaleY, props.originX, props.originY);
|
||||
|
||||
if (props.transform != null) {
|
||||
pooledTransform.transform(props.transform);
|
||||
}
|
||||
|
||||
return [
|
||||
pooledTransform.xx, pooledTransform.yx,
|
||||
pooledTransform.xy, pooledTransform.yy,
|
||||
pooledTransform.x, pooledTransform.y,
|
||||
];
|
||||
}
|
||||
|
||||
function extractOpacity(props) {
|
||||
// TODO: visible === false should also have no hit detection
|
||||
if (props.visible === false) {
|
||||
return 0;
|
||||
}
|
||||
if (props.opacity == null) {
|
||||
return 1;
|
||||
}
|
||||
return +props.opacity;
|
||||
}
|
||||
|
||||
// Groups
|
||||
|
||||
// Note: ART has a notion of width and height on Group but AFAIK it's a noop in
|
||||
// ReactART.
|
||||
|
||||
class Group extends React.Component {
|
||||
static contextTypes = {
|
||||
isInSurface: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
render() {
|
||||
var props = this.props;
|
||||
invariant(
|
||||
this.context.isInSurface,
|
||||
'ART: <Group /> must be a child of a <Surface />'
|
||||
);
|
||||
return (
|
||||
<NativeGroup
|
||||
opacity={extractOpacity(props)}
|
||||
transform={extractTransform(props)}>
|
||||
{this.props.children}
|
||||
</NativeGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ClippingRectangle extends React.Component {
|
||||
render() {
|
||||
var props = this.props;
|
||||
var x = extractNumber(props.x, 0);
|
||||
var y = extractNumber(props.y, 0);
|
||||
var w = extractNumber(props.width, 0);
|
||||
var h = extractNumber(props.height, 0);
|
||||
var clipping = [x, y, w, h];
|
||||
// The current clipping API requires x and y to be ignored in the transform
|
||||
var propsExcludingXAndY = merge(props);
|
||||
delete propsExcludingXAndY.x;
|
||||
delete propsExcludingXAndY.y;
|
||||
return (
|
||||
<NativeGroup
|
||||
clipping={clipping}
|
||||
opacity={extractOpacity(props)}
|
||||
transform={extractTransform(propsExcludingXAndY)}>
|
||||
{this.props.children}
|
||||
</NativeGroup>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Renderables
|
||||
|
||||
var SOLID_COLOR = 0;
|
||||
var LINEAR_GRADIENT = 1;
|
||||
var RADIAL_GRADIENT = 2;
|
||||
var PATTERN = 3;
|
||||
|
||||
function insertColorIntoArray(color, targetArray, atIndex) {
|
||||
var c = new Color(color);
|
||||
targetArray[atIndex + 0] = c.red / 255;
|
||||
targetArray[atIndex + 1] = c.green / 255;
|
||||
targetArray[atIndex + 2] = c.blue / 255;
|
||||
targetArray[atIndex + 3] = c.alpha;
|
||||
}
|
||||
|
||||
function insertColorsIntoArray(stops, targetArray, atIndex) {
|
||||
var i = 0;
|
||||
if ('length' in stops) {
|
||||
while (i < stops.length) {
|
||||
insertColorIntoArray(stops[i], targetArray, atIndex + i * 4);
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
for (var offset in stops) {
|
||||
insertColorIntoArray(stops[offset], targetArray, atIndex + i * 4);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return atIndex + i * 4;
|
||||
}
|
||||
|
||||
function insertOffsetsIntoArray(stops, targetArray, atIndex, multi, reverse) {
|
||||
var offsetNumber;
|
||||
var i = 0;
|
||||
if ('length' in stops) {
|
||||
while (i < stops.length) {
|
||||
offsetNumber = i / (stops.length - 1) * multi;
|
||||
targetArray[atIndex + i] = reverse ? 1 - offsetNumber : offsetNumber;
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
for (var offsetString in stops) {
|
||||
offsetNumber = (+offsetString) * multi;
|
||||
targetArray[atIndex + i] = reverse ? 1 - offsetNumber : offsetNumber;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return atIndex + i;
|
||||
}
|
||||
|
||||
function insertColorStopsIntoArray(stops, targetArray, atIndex) {
|
||||
var lastIndex = insertColorsIntoArray(stops, targetArray, atIndex);
|
||||
insertOffsetsIntoArray(stops, targetArray, lastIndex, 1, false);
|
||||
}
|
||||
|
||||
function insertDoubleColorStopsIntoArray(stops, targetArray, atIndex) {
|
||||
var lastIndex = insertColorsIntoArray(stops, targetArray, atIndex);
|
||||
lastIndex = insertColorsIntoArray(stops, targetArray, lastIndex);
|
||||
lastIndex = insertOffsetsIntoArray(stops, targetArray, lastIndex, 0.5, false);
|
||||
insertOffsetsIntoArray(stops, targetArray, lastIndex, 0.5, true);
|
||||
}
|
||||
|
||||
function applyBoundingBoxToBrushData(brushData, props) {
|
||||
var type = brushData[0];
|
||||
var width = +props.width;
|
||||
var height = +props.height;
|
||||
if (type === LINEAR_GRADIENT) {
|
||||
brushData[1] *= width;
|
||||
brushData[2] *= height;
|
||||
brushData[3] *= width;
|
||||
brushData[4] *= height;
|
||||
} else if (type === RADIAL_GRADIENT) {
|
||||
brushData[1] *= width;
|
||||
brushData[2] *= height;
|
||||
brushData[3] *= width;
|
||||
brushData[4] *= height;
|
||||
brushData[5] *= width;
|
||||
brushData[6] *= height;
|
||||
} else if (type === PATTERN) {
|
||||
// todo
|
||||
}
|
||||
}
|
||||
|
||||
function extractBrush(colorOrBrush, props) {
|
||||
if (colorOrBrush == null) {
|
||||
return null;
|
||||
}
|
||||
if (colorOrBrush._brush) {
|
||||
if (colorOrBrush._bb) {
|
||||
// The legacy API for Gradients allow for the bounding box to be used
|
||||
// as a convenience for specifying gradient positions. This should be
|
||||
// deprecated. It's not properly implemented in canvas mode. ReactART
|
||||
// doesn't handle update to the bounding box correctly. That's why we
|
||||
// mutate this so that if it's reused, we reuse the same resolved box.
|
||||
applyBoundingBoxToBrushData(colorOrBrush._brush, props);
|
||||
colorOrBrush._bb = false;
|
||||
}
|
||||
return colorOrBrush._brush;
|
||||
}
|
||||
var c = new Color(colorOrBrush);
|
||||
return [SOLID_COLOR, c.red / 255, c.green / 255, c.blue / 255, c.alpha];
|
||||
}
|
||||
|
||||
function extractColor(color) {
|
||||
if (color == null) {
|
||||
return null;
|
||||
}
|
||||
var c = new Color(color);
|
||||
return [c.red / 255, c.green / 255, c.blue / 255, c.alpha];
|
||||
}
|
||||
|
||||
function extractStrokeCap(strokeCap) {
|
||||
switch (strokeCap) {
|
||||
case 'butt': return 0;
|
||||
case 'square': return 2;
|
||||
default: return 1; // round
|
||||
}
|
||||
}
|
||||
|
||||
function extractStrokeJoin(strokeJoin) {
|
||||
switch (strokeJoin) {
|
||||
case 'miter': return 0;
|
||||
case 'bevel': return 2;
|
||||
default: return 1; // round
|
||||
}
|
||||
}
|
||||
|
||||
// Shape
|
||||
|
||||
// Note: ART has a notion of width and height on Shape but AFAIK it's a noop in
|
||||
// ReactART.
|
||||
|
||||
class Shape extends React.Component {
|
||||
render() {
|
||||
var props = this.props;
|
||||
var path = props.d || childrenAsString(props.children);
|
||||
var d = (path instanceof Path ? path : new Path(path)).toJSON();
|
||||
return (
|
||||
<NativeShape
|
||||
fill={extractBrush(props.fill, props)}
|
||||
opacity={extractOpacity(props)}
|
||||
stroke={extractColor(props.stroke)}
|
||||
strokeCap={extractStrokeCap(props.strokeCap)}
|
||||
strokeDash={props.strokeDash || null}
|
||||
strokeJoin={extractStrokeJoin(props.strokeJoin)}
|
||||
strokeWidth={extractNumber(props.strokeWidth, 1)}
|
||||
transform={extractTransform(props)}
|
||||
|
||||
d={d}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Text
|
||||
|
||||
var cachedFontObjectsFromString = {};
|
||||
|
||||
var fontFamilyPrefix = /^[\s"']*/;
|
||||
var fontFamilySuffix = /[\s"']*$/;
|
||||
|
||||
function extractSingleFontFamily(fontFamilyString) {
|
||||
// ART on the web allows for multiple font-families to be specified.
|
||||
// For compatibility, we extract the first font-family, hoping
|
||||
// we'll get a match.
|
||||
return fontFamilyString.split(',')[0]
|
||||
.replace(fontFamilyPrefix, '')
|
||||
.replace(fontFamilySuffix, '');
|
||||
}
|
||||
|
||||
function parseFontString(font) {
|
||||
if (cachedFontObjectsFromString.hasOwnProperty(font)) {
|
||||
return cachedFontObjectsFromString[font];
|
||||
}
|
||||
var regexp = /^\s*((?:(?:normal|bold|italic)\s+)*)(?:(\d+(?:\.\d+)?)[ptexm\%]*(?:\s*\/.*?)?\s+)?\s*\"?([^\"]*)/i;
|
||||
var match = regexp.exec(font);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
var fontFamily = extractSingleFontFamily(match[3]);
|
||||
var fontSize = +match[2] || 12;
|
||||
var isBold = /bold/.exec(match[1]);
|
||||
var isItalic = /italic/.exec(match[1]);
|
||||
cachedFontObjectsFromString[font] = {
|
||||
fontFamily: fontFamily,
|
||||
fontSize: fontSize,
|
||||
fontWeight: isBold ? 'bold' : 'normal',
|
||||
fontStyle: isItalic ? 'italic' : 'normal',
|
||||
};
|
||||
return cachedFontObjectsFromString[font];
|
||||
}
|
||||
|
||||
function extractFont(font) {
|
||||
if (font == null) {
|
||||
return null;
|
||||
}
|
||||
if (typeof font === 'string') {
|
||||
return parseFontString(font);
|
||||
}
|
||||
var fontFamily = extractSingleFontFamily(font.fontFamily);
|
||||
var fontSize = +font.fontSize || 12;
|
||||
var fontWeight = font.fontWeight != null ? font.fontWeight.toString() : '400';
|
||||
return {
|
||||
// Normalize
|
||||
fontFamily: fontFamily,
|
||||
fontSize: fontSize,
|
||||
fontWeight: fontWeight,
|
||||
fontStyle: font.fontStyle,
|
||||
};
|
||||
}
|
||||
|
||||
var newLine = /\n/g;
|
||||
function extractFontAndLines(font, text) {
|
||||
return { font: extractFont(font), lines: text.split(newLine) };
|
||||
}
|
||||
|
||||
function extractAlignment(alignment) {
|
||||
switch (alignment) {
|
||||
case 'right':
|
||||
return 1;
|
||||
case 'center':
|
||||
return 2;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
class Text extends React.Component {
|
||||
render() {
|
||||
var props = this.props;
|
||||
var path = props.path;
|
||||
var textPath = path ? (path instanceof Path ? path : new Path(path)).toJSON() : null;
|
||||
var textFrame = extractFontAndLines(
|
||||
props.font,
|
||||
childrenAsString(props.children)
|
||||
);
|
||||
return (
|
||||
<NativeText
|
||||
fill={extractBrush(props.fill, props)}
|
||||
opacity={extractOpacity(props)}
|
||||
stroke={extractColor(props.stroke)}
|
||||
strokeCap={extractStrokeCap(props.strokeCap)}
|
||||
strokeDash={props.strokeDash || null}
|
||||
strokeJoin={extractStrokeJoin(props.strokeJoin)}
|
||||
strokeWidth={extractNumber(props.strokeWidth, 1)}
|
||||
transform={extractTransform(props)}
|
||||
|
||||
alignment={extractAlignment(props.alignment)}
|
||||
frame={textFrame}
|
||||
path={textPath}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Declarative fill type objects - API design not finalized
|
||||
|
||||
function LinearGradient(stops, x1, y1, x2, y2) {
|
||||
var type = LINEAR_GRADIENT;
|
||||
|
||||
if (arguments.length < 5) {
|
||||
var angle = ((x1 == null) ? 270 : x1) * Math.PI / 180;
|
||||
|
||||
var x = Math.cos(angle);
|
||||
var y = -Math.sin(angle);
|
||||
var l = (Math.abs(x) + Math.abs(y)) / 2;
|
||||
|
||||
x *= l; y *= l;
|
||||
|
||||
x1 = 0.5 - x;
|
||||
x2 = 0.5 + x;
|
||||
y1 = 0.5 - y;
|
||||
y2 = 0.5 + y;
|
||||
this._bb = true;
|
||||
} else {
|
||||
this._bb = false;
|
||||
}
|
||||
|
||||
var brushData = [type, +x1, +y1, +x2, +y2];
|
||||
insertColorStopsIntoArray(stops, brushData, 5);
|
||||
this._brush = brushData;
|
||||
}
|
||||
|
||||
function RadialGradient(stops, fx, fy, rx, ry, cx, cy) {
|
||||
if (ry == null) {
|
||||
ry = rx;
|
||||
}
|
||||
if (cx == null) {
|
||||
cx = fx;
|
||||
}
|
||||
if (cy == null) {
|
||||
cy = fy;
|
||||
}
|
||||
if (fx == null) {
|
||||
// As a convenience we allow the whole radial gradient to cover the
|
||||
// bounding box. We should consider dropping this API.
|
||||
fx = fy = rx = ry = cx = cy = 0.5;
|
||||
this._bb = true;
|
||||
} else {
|
||||
this._bb = false;
|
||||
}
|
||||
// The ART API expects the radial gradient to be repeated at the edges.
|
||||
// To simulate this we render the gradient twice as large and add double
|
||||
// color stops. Ideally this API would become more restrictive so that this
|
||||
// extra work isn't needed.
|
||||
var brushData = [RADIAL_GRADIENT, +fx, +fy, +rx * 2, +ry * 2, +cx, +cy];
|
||||
insertDoubleColorStopsIntoArray(stops, brushData, 7);
|
||||
this._brush = brushData;
|
||||
}
|
||||
|
||||
function Pattern(url, width, height, left, top) {
|
||||
this._brush = [PATTERN, url, +left || 0, +top || 0, +width, +height];
|
||||
}
|
||||
|
||||
var ReactART = {
|
||||
LinearGradient: LinearGradient,
|
||||
RadialGradient: RadialGradient,
|
||||
Pattern: Pattern,
|
||||
Transform: Transform,
|
||||
Path: Path,
|
||||
Surface: Surface,
|
||||
Group: Group,
|
||||
ClippingRectangle: ClippingRectangle,
|
||||
Shape: Shape,
|
||||
Text: Text,
|
||||
};
|
||||
|
||||
module.exports = ReactART;
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTNodeManager.h"
|
||||
|
||||
@interface ARTGroupManager : ARTNodeManager
|
||||
|
||||
@end
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTGroupManager.h"
|
||||
|
||||
#import "ARTGroup.h"
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTGroupManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (ARTNode *)node
|
||||
{
|
||||
return [ARTGroup new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(clipping, CGRect)
|
||||
|
||||
@end
|
||||
@@ -1,18 +0,0 @@
|
||||
/**
|
||||
* 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 <React/RCTViewManager.h>
|
||||
|
||||
@class ARTNode;
|
||||
|
||||
@interface ARTNodeManager : RCTViewManager
|
||||
|
||||
- (ARTNode *)node;
|
||||
|
||||
@end
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTNodeManager.h"
|
||||
|
||||
#import "ARTNode.h"
|
||||
|
||||
@implementation ARTNodeManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (ARTNode *)node
|
||||
{
|
||||
return [ARTNode new];
|
||||
}
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [self node];
|
||||
}
|
||||
|
||||
- (RCTShadowView *)shadowView
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(opacity, CGFloat)
|
||||
RCT_EXPORT_VIEW_PROPERTY(transform, CGAffineTransform)
|
||||
|
||||
@end
|
||||
@@ -1,17 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTNodeManager.h"
|
||||
#import "ARTRenderable.h"
|
||||
|
||||
@interface ARTRenderableManager : ARTNodeManager
|
||||
|
||||
- (ARTRenderable *)node;
|
||||
|
||||
@end
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTRenderableManager.h"
|
||||
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTRenderableManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (ARTRenderable *)node
|
||||
{
|
||||
return [ARTRenderable new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(strokeWidth, CGFloat)
|
||||
RCT_EXPORT_VIEW_PROPERTY(strokeCap, CGLineCap)
|
||||
RCT_EXPORT_VIEW_PROPERTY(strokeJoin, CGLineJoin)
|
||||
RCT_EXPORT_VIEW_PROPERTY(fill, ARTBrush)
|
||||
RCT_EXPORT_VIEW_PROPERTY(stroke, CGColor)
|
||||
RCT_EXPORT_VIEW_PROPERTY(strokeDash, ARTCGFloatArray)
|
||||
|
||||
@end
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTRenderableManager.h"
|
||||
|
||||
@interface ARTShapeManager : ARTRenderableManager
|
||||
|
||||
@end
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTShapeManager.h"
|
||||
|
||||
#import "ARTShape.h"
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTShapeManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (ARTRenderable *)node
|
||||
{
|
||||
return [ARTShape new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(d, CGPath)
|
||||
|
||||
@end
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* 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 <React/RCTViewManager.h>
|
||||
|
||||
@interface ARTSurfaceViewManager : RCTViewManager
|
||||
|
||||
@end
|
||||
@@ -1,23 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTSurfaceViewManager.h"
|
||||
|
||||
#import "ARTSurfaceView.h"
|
||||
|
||||
@implementation ARTSurfaceViewManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [ARTSurfaceView new];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,14 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTRenderableManager.h"
|
||||
|
||||
@interface ARTTextManager : ARTRenderableManager
|
||||
|
||||
@end
|
||||
@@ -1,27 +0,0 @@
|
||||
/**
|
||||
* 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 "ARTTextManager.h"
|
||||
|
||||
#import "ARTText.h"
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTTextManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (ARTRenderable *)node
|
||||
{
|
||||
return [ARTText new];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(alignment, CTTextAlignment)
|
||||
RCT_REMAP_VIEW_PROPERTY(frame, textFrame, ARTTextFrame)
|
||||
|
||||
@end
|
||||
@@ -1,84 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule ActionSheetIOS
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var RCTActionSheetManager = require('NativeModules').ActionSheetManager;
|
||||
|
||||
var invariant = require('fbjs/lib/invariant');
|
||||
var processColor = require('processColor');
|
||||
|
||||
var ActionSheetIOS = {
|
||||
/**
|
||||
* Display an iOS action sheet. The `options` object must contain one or more
|
||||
* of:
|
||||
*
|
||||
* - `options` (array of strings) - a list of button titles (required)
|
||||
* - `cancelButtonIndex` (int) - index of cancel button in `options`
|
||||
* - `destructiveButtonIndex` (int) - index of destructive button in `options`
|
||||
* - `title` (string) - a title to show above the action sheet
|
||||
* - `message` (string) - a message to show below the title
|
||||
*/
|
||||
showActionSheetWithOptions(options: Object, callback: Function) {
|
||||
invariant(
|
||||
typeof options === 'object' && options !== null,
|
||||
'Options must be a valid object'
|
||||
);
|
||||
invariant(
|
||||
typeof callback === 'function',
|
||||
'Must provide a valid callback'
|
||||
);
|
||||
RCTActionSheetManager.showActionSheetWithOptions(
|
||||
{...options, tintColor: processColor(options.tintColor)},
|
||||
callback
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Display the iOS share sheet. The `options` object should contain
|
||||
* one or both of `message` and `url` and can additionally have
|
||||
* a `subject` or `excludedActivityTypes`:
|
||||
*
|
||||
* - `url` (string) - a URL to share
|
||||
* - `message` (string) - a message to share
|
||||
* - `subject` (string) - a subject for the message
|
||||
* - `excludedActivityTypes` (array) - the activities to exclude from the ActionSheet
|
||||
*
|
||||
* NOTE: if `url` points to a local file, or is a base64-encoded
|
||||
* uri, the file it points to will be loaded and shared directly.
|
||||
* In this way, you can share images, videos, PDF files, etc.
|
||||
*/
|
||||
showShareActionSheetWithOptions(
|
||||
options: Object,
|
||||
failureCallback: Function,
|
||||
successCallback: Function
|
||||
) {
|
||||
invariant(
|
||||
typeof options === 'object' && options !== null,
|
||||
'Options must be a valid object'
|
||||
);
|
||||
invariant(
|
||||
typeof failureCallback === 'function',
|
||||
'Must provide a valid failureCallback'
|
||||
);
|
||||
invariant(
|
||||
typeof successCallback === 'function',
|
||||
'Must provide a valid successCallback'
|
||||
);
|
||||
RCTActionSheetManager.showShareActionSheetWithOptions(
|
||||
{...options, tintColor: processColor(options.tintColor)},
|
||||
failureCallback,
|
||||
successCallback
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = ActionSheetIOS;
|
||||
@@ -1,235 +0,0 @@
|
||||
// !$*UTF8*$!
|
||||
{
|
||||
archiveVersion = 1;
|
||||
classes = {
|
||||
};
|
||||
objectVersion = 46;
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
14C644C41AB0DFC900DE3C65 /* RCTActionSheetManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 14C644C21AB0DFC900DE3C65 /* RCTActionSheetManager.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
134814201AA4EA6300B7C361 /* libRCTActionSheet.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTActionSheet.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
14C644C11AB0DFC900DE3C65 /* RCTActionSheetManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTActionSheetManager.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
|
||||
14C644C21AB0DFC900DE3C65 /* RCTActionSheetManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTActionSheetManager.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
134814211AA4EA7D00B7C361 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
134814201AA4EA6300B7C361 /* libRCTActionSheet.a */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
58B511D21A9E6C8500147676 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
14C644C11AB0DFC900DE3C65 /* RCTActionSheetManager.h */,
|
||||
14C644C21AB0DFC900DE3C65 /* RCTActionSheetManager.m */,
|
||||
134814211AA4EA7D00B7C361 /* Products */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
sourceTree = "<group>";
|
||||
tabWidth = 2;
|
||||
usesTabs = 0;
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
58B511DA1A9E6C8500147676 /* RCTActionSheet */ = {
|
||||
isa = PBXNativeTarget;
|
||||
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTActionSheet" */;
|
||||
buildPhases = (
|
||||
58B511D71A9E6C8500147676 /* Sources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
);
|
||||
name = RCTActionSheet;
|
||||
productName = RCTDataManager;
|
||||
productReference = 134814201AA4EA6300B7C361 /* libRCTActionSheet.a */;
|
||||
productType = "com.apple.product-type.library.static";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
58B511D31A9E6C8500147676 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0610;
|
||||
ORGANIZATIONNAME = Facebook;
|
||||
TargetAttributes = {
|
||||
58B511DA1A9E6C8500147676 = {
|
||||
CreatedOnToolsVersion = 6.1.1;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTActionSheet" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
);
|
||||
mainGroup = 58B511D21A9E6C8500147676;
|
||||
productRefGroup = 58B511D21A9E6C8500147676;
|
||||
projectDirPath = "";
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
58B511DA1A9E6C8500147676 /* RCTActionSheet */,
|
||||
);
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXSourcesBuildPhase section */
|
||||
58B511D71A9E6C8500147676 /* Sources */ = {
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
14C644C41AB0DFC900DE3C65 /* RCTActionSheetManager.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;
|
||||
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 = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
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 = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SKIP_INSTALL = YES;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
WARNING_CFLAGS = (
|
||||
"-Werror",
|
||||
"-Wall",
|
||||
);
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
58B511F01A9E6C8500147676 /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = RCTActionSheet;
|
||||
RUN_CLANG_STATIC_ANALYZER = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
58B511F11A9E6C8500147676 /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
CLANG_STATIC_ANALYZER_MODE = deep;
|
||||
LIBRARY_SEARCH_PATHS = "$(inherited)";
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = RCTActionSheet;
|
||||
RUN_CLANG_STATIC_ANALYZER = NO;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
/* End XCBuildConfiguration section */
|
||||
|
||||
/* Begin XCConfigurationList section */
|
||||
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RCTActionSheet" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
58B511ED1A9E6C8500147676 /* Debug */,
|
||||
58B511EE1A9E6C8500147676 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RCTActionSheet" */ = {
|
||||
isa = XCConfigurationList;
|
||||
buildConfigurations = (
|
||||
58B511F01A9E6C8500147676 /* Debug */,
|
||||
58B511F11A9E6C8500147676 /* Release */,
|
||||
);
|
||||
defaultConfigurationIsVisible = 0;
|
||||
defaultConfigurationName = Release;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* 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 <UIKit/UIKit.h>
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
|
||||
@interface RCTActionSheetManager : NSObject <RCTBridgeModule>
|
||||
|
||||
@end
|
||||
@@ -1,204 +0,0 @@
|
||||
/**
|
||||
* 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 "RCTActionSheetManager.h"
|
||||
|
||||
#import <React/RCTBridge.h>
|
||||
#import <React/RCTConvert.h>
|
||||
#import <React/RCTLog.h>
|
||||
#import <React/RCTUIManager.h>
|
||||
#import <React/RCTUtils.h>
|
||||
|
||||
@interface RCTActionSheetManager () <UIActionSheetDelegate>
|
||||
@end
|
||||
|
||||
@implementation RCTActionSheetManager
|
||||
{
|
||||
// Use NSMapTable, as UIAlertViews do not implement <NSCopying>
|
||||
// which is required for NSDictionary keys
|
||||
NSMapTable *_callbacks;
|
||||
}
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
@synthesize bridge = _bridge;
|
||||
|
||||
- (dispatch_queue_t)methodQueue
|
||||
{
|
||||
return dispatch_get_main_queue();
|
||||
}
|
||||
|
||||
/*
|
||||
* The `anchor` option takes a view to set as the anchor for the share
|
||||
* popup to point to, on iPads running iOS 8. If it is not passed, it
|
||||
* defaults to centering the share popup on screen without any arrows.
|
||||
*/
|
||||
- (CGRect)sourceRectInView:(UIView *)sourceView
|
||||
anchorViewTag:(NSNumber *)anchorViewTag
|
||||
{
|
||||
if (anchorViewTag) {
|
||||
UIView *anchorView = [self.bridge.uiManager viewForReactTag:anchorViewTag];
|
||||
return [anchorView convertRect:anchorView.bounds toView:sourceView];
|
||||
} else {
|
||||
return (CGRect){sourceView.center, {1, 1}};
|
||||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(showActionSheetWithOptions:(NSDictionary *)options
|
||||
callback:(RCTResponseSenderBlock)callback)
|
||||
{
|
||||
if (RCTRunningInAppExtension()) {
|
||||
RCTLogError(@"Unable to show action sheet from app extension");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_callbacks) {
|
||||
_callbacks = [NSMapTable strongToStrongObjectsMapTable];
|
||||
}
|
||||
|
||||
NSString *title = [RCTConvert NSString:options[@"title"]];
|
||||
NSString *message = [RCTConvert NSString:options[@"message"]];
|
||||
NSArray<NSString *> *buttons = [RCTConvert NSStringArray:options[@"options"]];
|
||||
NSInteger destructiveButtonIndex = options[@"destructiveButtonIndex"] ? [RCTConvert NSInteger:options[@"destructiveButtonIndex"]] : -1;
|
||||
NSInteger cancelButtonIndex = options[@"cancelButtonIndex"] ? [RCTConvert NSInteger:options[@"cancelButtonIndex"]] : -1;
|
||||
|
||||
UIViewController *controller = RCTPresentedViewController();
|
||||
|
||||
if (controller == nil) {
|
||||
RCTLogError(@"Tried to display action sheet but there is no application window. options: %@", options);
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The `anchor` option takes a view to set as the anchor for the share
|
||||
* popup to point to, on iPads running iOS 8. If it is not passed, it
|
||||
* defaults to centering the share popup on screen without any arrows.
|
||||
*/
|
||||
NSNumber *anchorViewTag = [RCTConvert NSNumber:options[@"anchor"]];
|
||||
UIView *sourceView = controller.view;
|
||||
CGRect sourceRect = [self sourceRectInView:sourceView anchorViewTag:anchorViewTag];
|
||||
|
||||
UIAlertController *alertController =
|
||||
[UIAlertController alertControllerWithTitle:title
|
||||
message:message
|
||||
preferredStyle:UIAlertControllerStyleActionSheet];
|
||||
|
||||
NSInteger index = 0;
|
||||
for (NSString *option in buttons) {
|
||||
UIAlertActionStyle style = UIAlertActionStyleDefault;
|
||||
if (index == destructiveButtonIndex) {
|
||||
style = UIAlertActionStyleDestructive;
|
||||
} else if (index == cancelButtonIndex) {
|
||||
style = UIAlertActionStyleCancel;
|
||||
}
|
||||
|
||||
NSInteger localIndex = index;
|
||||
[alertController addAction:[UIAlertAction actionWithTitle:option
|
||||
style:style
|
||||
handler:^(__unused UIAlertAction *action){
|
||||
callback(@[@(localIndex)]);
|
||||
}]];
|
||||
|
||||
index++;
|
||||
}
|
||||
|
||||
alertController.modalPresentationStyle = UIModalPresentationPopover;
|
||||
alertController.popoverPresentationController.sourceView = sourceView;
|
||||
alertController.popoverPresentationController.sourceRect = sourceRect;
|
||||
if (!anchorViewTag) {
|
||||
alertController.popoverPresentationController.permittedArrowDirections = 0;
|
||||
}
|
||||
[controller presentViewController:alertController animated:YES completion:nil];
|
||||
|
||||
alertController.view.tintColor = [RCTConvert UIColor:options[@"tintColor"]];
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(showShareActionSheetWithOptions:(NSDictionary *)options
|
||||
failureCallback:(RCTResponseErrorBlock)failureCallback
|
||||
successCallback:(RCTResponseSenderBlock)successCallback)
|
||||
{
|
||||
if (RCTRunningInAppExtension()) {
|
||||
RCTLogError(@"Unable to show action sheet from app extension");
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableArray<id> *items = [NSMutableArray array];
|
||||
NSString *message = [RCTConvert NSString:options[@"message"]];
|
||||
if (message) {
|
||||
[items addObject:message];
|
||||
}
|
||||
NSURL *URL = [RCTConvert NSURL:options[@"url"]];
|
||||
if (URL) {
|
||||
if ([URL.scheme.lowercaseString isEqualToString:@"data"]) {
|
||||
NSError *error;
|
||||
NSData *data = [NSData dataWithContentsOfURL:URL
|
||||
options:(NSDataReadingOptions)0
|
||||
error:&error];
|
||||
if (!data) {
|
||||
failureCallback(error);
|
||||
return;
|
||||
}
|
||||
[items addObject:data];
|
||||
} else {
|
||||
[items addObject:URL];
|
||||
}
|
||||
}
|
||||
if (items.count == 0) {
|
||||
RCTLogError(@"No `url` or `message` to share");
|
||||
return;
|
||||
}
|
||||
|
||||
UIActivityViewController *shareController = [[UIActivityViewController alloc] initWithActivityItems:items applicationActivities:nil];
|
||||
|
||||
NSString *subject = [RCTConvert NSString:options[@"subject"]];
|
||||
if (subject) {
|
||||
[shareController setValue:subject forKey:@"subject"];
|
||||
}
|
||||
|
||||
NSArray *excludedActivityTypes = [RCTConvert NSStringArray:options[@"excludedActivityTypes"]];
|
||||
if (excludedActivityTypes) {
|
||||
shareController.excludedActivityTypes = excludedActivityTypes;
|
||||
}
|
||||
|
||||
UIViewController *controller = RCTPresentedViewController();
|
||||
shareController.completionWithItemsHandler = ^(NSString *activityType, BOOL completed, __unused NSArray *returnedItems, NSError *activityError) {
|
||||
if (activityError) {
|
||||
failureCallback(activityError);
|
||||
} else {
|
||||
successCallback(@[@(completed), RCTNullIfNil(activityType)]);
|
||||
}
|
||||
};
|
||||
|
||||
shareController.modalPresentationStyle = UIModalPresentationPopover;
|
||||
NSNumber *anchorViewTag = [RCTConvert NSNumber:options[@"anchor"]];
|
||||
if (!anchorViewTag) {
|
||||
shareController.popoverPresentationController.permittedArrowDirections = 0;
|
||||
}
|
||||
shareController.popoverPresentationController.sourceView = controller.view;
|
||||
shareController.popoverPresentationController.sourceRect = [self sourceRectInView:controller.view anchorViewTag:anchorViewTag];
|
||||
|
||||
[controller presentViewController:shareController animated:YES completion:nil];
|
||||
|
||||
shareController.view.tintColor = [RCTConvert UIColor:options[@"tintColor"]];
|
||||
}
|
||||
|
||||
#pragma mark UIActionSheetDelegate Methods
|
||||
|
||||
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
|
||||
{
|
||||
RCTResponseSenderBlock callback = [_callbacks objectForKey:actionSheet];
|
||||
if (callback) {
|
||||
callback(@[@(buttonIndex)]);
|
||||
[_callbacks removeObjectForKey:actionSheet];
|
||||
} else {
|
||||
RCTLogWarn(@"No callback registered for action sheet: %@", actionSheet.title);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,155 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule Alert
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AlertIOS = require('AlertIOS');
|
||||
const NativeModules = require('NativeModules');
|
||||
const Platform = require('Platform');
|
||||
|
||||
import type { AlertType, AlertButtonStyle } from 'AlertIOS';
|
||||
|
||||
type Buttons = Array<{
|
||||
text?: string,
|
||||
onPress?: ?Function,
|
||||
style?: AlertButtonStyle,
|
||||
}>;
|
||||
|
||||
type Options = {
|
||||
cancelable?: ?boolean,
|
||||
onDismiss?: ?Function,
|
||||
};
|
||||
|
||||
/**
|
||||
* Launches an alert dialog with the specified title and message.
|
||||
*
|
||||
* Optionally provide a list of buttons. Tapping any button will fire the
|
||||
* respective onPress callback and dismiss the alert. By default, the only
|
||||
* button will be an 'OK' button.
|
||||
*
|
||||
* This is an API that works both on iOS and Android and can show static
|
||||
* alerts. To show an alert that prompts the user to enter some information,
|
||||
* see `AlertIOS`; entering text in an alert is common on iOS only.
|
||||
*
|
||||
* ## iOS
|
||||
*
|
||||
* On iOS you can specify any number of buttons. Each button can optionally
|
||||
* specify a style, which is one of 'default', 'cancel' or 'destructive'.
|
||||
*
|
||||
* ## Android
|
||||
*
|
||||
* On Android at most three buttons can be specified. Android has a concept
|
||||
* of a neutral, negative and a positive button:
|
||||
*
|
||||
* - If you specify one button, it will be the 'positive' one (such as 'OK')
|
||||
* - Two buttons mean 'negative', 'positive' (such as 'Cancel', 'OK')
|
||||
* - Three buttons mean 'neutral', 'negative', 'positive' (such as 'Later', 'Cancel', 'OK')
|
||||
*
|
||||
* By default alerts on Android can be dismissed by tapping outside of the alert
|
||||
* box. This event can be handled by providing an optional `options` parameter,
|
||||
* with an `onDismiss` callback property `{ onDismiss: () => {} }`.
|
||||
*
|
||||
* Alternatively, the dismissing behavior can be disabled altogether by providing
|
||||
* an optional `options` parameter with the `cancelable` property set to `false`
|
||||
* i.e. `{ cancelable: false }`
|
||||
*
|
||||
* Example usage:
|
||||
* ```
|
||||
* // Works on both iOS and Android
|
||||
* Alert.alert(
|
||||
* 'Alert Title',
|
||||
* 'My Alert Msg',
|
||||
* [
|
||||
* {text: 'Ask me later', onPress: () => console.log('Ask me later pressed')},
|
||||
* {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'},
|
||||
* {text: 'OK', onPress: () => console.log('OK Pressed')},
|
||||
* ],
|
||||
* { cancelable: false }
|
||||
* )
|
||||
* ```
|
||||
*/
|
||||
class Alert {
|
||||
|
||||
static alert(
|
||||
title: ?string,
|
||||
message?: ?string,
|
||||
buttons?: Buttons,
|
||||
options?: Options,
|
||||
type?: AlertType,
|
||||
): void {
|
||||
if (Platform.OS === 'ios') {
|
||||
if (typeof type !== 'undefined') {
|
||||
console.warn('Alert.alert() with a 5th "type" parameter is deprecated and will be removed. Use AlertIOS.prompt() instead.');
|
||||
AlertIOS.alert(title, message, buttons, type);
|
||||
return;
|
||||
}
|
||||
AlertIOS.alert(title, message, buttons);
|
||||
} else if (Platform.OS === 'android') {
|
||||
AlertAndroid.alert(title, message, buttons, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around the Android native module.
|
||||
*/
|
||||
class AlertAndroid {
|
||||
|
||||
static alert(
|
||||
title: ?string,
|
||||
message?: ?string,
|
||||
buttons?: Buttons,
|
||||
options?: Options,
|
||||
): void {
|
||||
var config = {
|
||||
title: title || '',
|
||||
message: message || '',
|
||||
};
|
||||
|
||||
if (options) {
|
||||
config = {...config, cancelable: options.cancelable};
|
||||
}
|
||||
// At most three buttons (neutral, negative, positive). Ignore rest.
|
||||
// The text 'OK' should be probably localized. iOS Alert does that in native.
|
||||
var validButtons: Buttons = buttons ? buttons.slice(0, 3) : [{text: 'OK'}];
|
||||
var buttonPositive = validButtons.pop();
|
||||
var buttonNegative = validButtons.pop();
|
||||
var buttonNeutral = validButtons.pop();
|
||||
if (buttonNeutral) {
|
||||
config = {...config, buttonNeutral: buttonNeutral.text || '' };
|
||||
}
|
||||
if (buttonNegative) {
|
||||
config = {...config, buttonNegative: buttonNegative.text || '' };
|
||||
}
|
||||
if (buttonPositive) {
|
||||
config = {...config, buttonPositive: buttonPositive.text || '' };
|
||||
}
|
||||
NativeModules.DialogManagerAndroid.showAlert(
|
||||
config,
|
||||
(errorMessage) => console.warn(errorMessage),
|
||||
(action, buttonKey) => {
|
||||
if (action === NativeModules.DialogManagerAndroid.buttonClicked) {
|
||||
if (buttonKey === NativeModules.DialogManagerAndroid.buttonNeutral) {
|
||||
buttonNeutral.onPress && buttonNeutral.onPress();
|
||||
} else if (buttonKey === NativeModules.DialogManagerAndroid.buttonNegative) {
|
||||
buttonNegative.onPress && buttonNegative.onPress();
|
||||
} else if (buttonKey === NativeModules.DialogManagerAndroid.buttonPositive) {
|
||||
buttonPositive.onPress && buttonPositive.onPress();
|
||||
}
|
||||
} else if (action === NativeModules.DialogManagerAndroid.dismissed) {
|
||||
options && options.onDismiss && options.onDismiss();
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Alert;
|
||||
@@ -1,262 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AlertIOS
|
||||
* @flow
|
||||
* @jsdoc
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var RCTAlertManager = require('NativeModules').AlertManager;
|
||||
|
||||
/**
|
||||
* An Alert button type
|
||||
*/
|
||||
export type AlertType = $Enum<{
|
||||
/**
|
||||
* Default alert with no inputs
|
||||
*/
|
||||
'default': string,
|
||||
/**
|
||||
* Plain text input alert
|
||||
*/
|
||||
'plain-text': string,
|
||||
/**
|
||||
* Secure text input alert
|
||||
*/
|
||||
'secure-text': string,
|
||||
/**
|
||||
* Login and password alert
|
||||
*/
|
||||
'login-password': string,
|
||||
}>;
|
||||
|
||||
/**
|
||||
* An Alert button style
|
||||
*/
|
||||
export type AlertButtonStyle = $Enum<{
|
||||
/**
|
||||
* Default button style
|
||||
*/
|
||||
'default': string,
|
||||
/**
|
||||
* Cancel button style
|
||||
*/
|
||||
'cancel': string,
|
||||
/**
|
||||
* Destructive button style
|
||||
*/
|
||||
'destructive': string,
|
||||
}>;
|
||||
|
||||
/**
|
||||
* Array or buttons
|
||||
* @typedef {Array} ButtonsArray
|
||||
* @property {string=} text Button label
|
||||
* @property {Function=} onPress Callback function when button pressed
|
||||
* @property {AlertButtonStyle=} style Button style
|
||||
*/
|
||||
export type ButtonsArray = Array<{
|
||||
/**
|
||||
* Button label
|
||||
*/
|
||||
text?: string,
|
||||
/**
|
||||
* Callback function when button pressed
|
||||
*/
|
||||
onPress?: ?Function,
|
||||
/**
|
||||
* Button style
|
||||
*/
|
||||
style?: AlertButtonStyle,
|
||||
}>;
|
||||
|
||||
/**
|
||||
* @description
|
||||
* `AlertIOS` provides functionality to create an iOS alert dialog with a
|
||||
* message or create a prompt for user input.
|
||||
*
|
||||
* Creating an iOS alert:
|
||||
*
|
||||
* ```
|
||||
* AlertIOS.alert(
|
||||
* 'Sync Complete',
|
||||
* 'All your data are belong to us.'
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* Creating an iOS prompt:
|
||||
*
|
||||
* ```
|
||||
* AlertIOS.prompt(
|
||||
* 'Enter a value',
|
||||
* null,
|
||||
* text => console.log("You entered "+text)
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* We recommend using the [`Alert.alert`](docs/alert.html) method for
|
||||
* cross-platform support if you don't need to create iOS-only prompts.
|
||||
*
|
||||
*/
|
||||
class AlertIOS {
|
||||
/**
|
||||
* Create and display a popup alert.
|
||||
* @static
|
||||
* @method alert
|
||||
* @param title The dialog's title.
|
||||
*
|
||||
* An empty string hides the title.
|
||||
* @param message An optional message that appears below
|
||||
* the dialog's title.
|
||||
* @param callbackOrButtons This optional argument should
|
||||
* be either a single-argument function or an array of buttons. If passed
|
||||
* a function, it will be called when the user taps 'OK'.
|
||||
*
|
||||
* If passed an array of button configurations, each button should include
|
||||
* a `text` key, as well as optional `onPress` and `style` keys. `style`
|
||||
* should be one of 'default', 'cancel' or 'destructive'.
|
||||
* @param type Deprecated, do not use.
|
||||
*
|
||||
* @example <caption>Example with custom buttons</caption>
|
||||
*
|
||||
* AlertIOS.alert(
|
||||
* 'Update available',
|
||||
* 'Keep your app up to date to enjoy the latest features',
|
||||
* [
|
||||
* {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'},
|
||||
* {text: 'Install', onPress: () => console.log('Install Pressed')},
|
||||
* ],
|
||||
* );
|
||||
*/
|
||||
static alert(
|
||||
title: ?string,
|
||||
message?: ?string,
|
||||
callbackOrButtons?: ?(() => void) | ButtonsArray,
|
||||
type?: AlertType,
|
||||
): void {
|
||||
if (typeof type !== 'undefined') {
|
||||
console.warn('AlertIOS.alert() with a 4th "type" parameter is deprecated and will be removed. Use AlertIOS.prompt() instead.');
|
||||
this.prompt(title, message, callbackOrButtons, type);
|
||||
return;
|
||||
}
|
||||
this.prompt(title, message, callbackOrButtons, 'default');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and display a prompt to enter some text.
|
||||
* @static
|
||||
* @method prompt
|
||||
* @param title The dialog's title.
|
||||
* @param message An optional message that appears above the text
|
||||
* input.
|
||||
* @param callbackOrButtons This optional argument should
|
||||
* be either a single-argument function or an array of buttons. If passed
|
||||
* a function, it will be called with the prompt's value when the user
|
||||
* taps 'OK'.
|
||||
*
|
||||
* If passed an array of button configurations, each button should include
|
||||
* a `text` key, as well as optional `onPress` and `style` keys (see
|
||||
* example). `style` should be one of 'default', 'cancel' or 'destructive'.
|
||||
* @param type This configures the text input. One of 'plain-text',
|
||||
* 'secure-text' or 'login-password'.
|
||||
* @param defaultValue The default text in text input.
|
||||
* @param keyboardType The keyboard type of first text field(if exists).
|
||||
* One of 'default', 'email-address', 'numeric', 'phone-pad',
|
||||
* 'ascii-capable', 'numbers-and-punctuation', 'url', 'number-pad',
|
||||
* 'name-phone-pad', 'decimal-pad', 'twitter' or 'web-search'.
|
||||
*
|
||||
* @example <caption>Example with custom buttons</caption>
|
||||
*
|
||||
* AlertIOS.prompt(
|
||||
* 'Enter password',
|
||||
* 'Enter your password to claim your $1.5B in lottery winnings',
|
||||
* [
|
||||
* {text: 'Cancel', onPress: () => console.log('Cancel Pressed'), style: 'cancel'},
|
||||
* {text: 'OK', onPress: password => console.log('OK Pressed, password: ' + password)},
|
||||
* ],
|
||||
* 'secure-text'
|
||||
* );
|
||||
*
|
||||
* @example <caption>Example with the default button and a custom callback</caption>
|
||||
*
|
||||
* AlertIOS.prompt(
|
||||
* 'Update username',
|
||||
* null,
|
||||
* text => console.log("Your username is "+text),
|
||||
* null,
|
||||
* 'default'
|
||||
* );
|
||||
*/
|
||||
static prompt(
|
||||
title: ?string,
|
||||
message?: ?string,
|
||||
callbackOrButtons?: ?((text: string) => void) | ButtonsArray,
|
||||
type?: ?AlertType = 'plain-text',
|
||||
defaultValue?: string,
|
||||
keyboardType?: string
|
||||
): void {
|
||||
if (typeof type === 'function') {
|
||||
console.warn(
|
||||
'You passed a callback function as the "type" argument to AlertIOS.prompt(). React Native is ' +
|
||||
'assuming you want to use the deprecated AlertIOS.prompt(title, defaultValue, buttons, callback) ' +
|
||||
'signature. The current signature is AlertIOS.prompt(title, message, callbackOrButtons, type, defaultValue, ' +
|
||||
'keyboardType) and the old syntax will be removed in a future version.');
|
||||
|
||||
var callback = type;
|
||||
var defaultValue = message;
|
||||
RCTAlertManager.alertWithArgs({
|
||||
title: title,
|
||||
type: 'plain-text',
|
||||
defaultValue,
|
||||
}, (id, value) => {
|
||||
callback(value);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
var callbacks = [];
|
||||
var buttons = [];
|
||||
var cancelButtonKey;
|
||||
var destructiveButtonKey;
|
||||
if (typeof callbackOrButtons === 'function') {
|
||||
callbacks = [callbackOrButtons];
|
||||
}
|
||||
else if (callbackOrButtons instanceof Array) {
|
||||
callbackOrButtons.forEach((btn, index) => {
|
||||
callbacks[index] = btn.onPress;
|
||||
if (btn.style === 'cancel') {
|
||||
cancelButtonKey = String(index);
|
||||
} else if (btn.style === 'destructive') {
|
||||
destructiveButtonKey = String(index);
|
||||
}
|
||||
if (btn.text || index < (callbackOrButtons || []).length - 1) {
|
||||
var btnDef = {};
|
||||
btnDef[index] = btn.text || '';
|
||||
buttons.push(btnDef);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RCTAlertManager.alertWithArgs({
|
||||
title: title,
|
||||
message: message || undefined,
|
||||
buttons,
|
||||
type: type || undefined,
|
||||
defaultValue,
|
||||
cancelButtonKey,
|
||||
destructiveButtonKey,
|
||||
keyboardType,
|
||||
}, (id, value) => {
|
||||
var cb = callbacks[id];
|
||||
cb && cb(value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AlertIOS;
|
||||
@@ -1,25 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule RCTAlertManager
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var NativeModules = require('NativeModules');
|
||||
|
||||
function emptyCallback() {}
|
||||
|
||||
module.exports = {
|
||||
alertWithArgs: function(args, callback) {
|
||||
// TODO(5998984): Polyfill it correctly with DialogManagerAndroid
|
||||
NativeModules.DialogManagerAndroid.showAlert(
|
||||
args,
|
||||
emptyCallback,
|
||||
callback || emptyCallback);
|
||||
},
|
||||
};
|
||||
@@ -1,16 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule RCTAlertManager
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var RCTAlertManager = require('NativeModules').AlertManager;
|
||||
|
||||
module.exports = RCTAlertManager;
|
||||
@@ -1,712 +0,0 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv='Content-type' content='text/html; charset=utf-8'>
|
||||
<title>Animated</title>
|
||||
<link href="./style.css" rel="stylesheet" type="text/css">
|
||||
<script src="https://fb.me/react-with-addons-0.13.3.js"></script>
|
||||
<script src="https://fb.me/JSXTransformer-0.13.3.js"></script>
|
||||
<script src="../release/dist/animated.js"></script>
|
||||
<style>
|
||||
|
||||
</style>
|
||||
<script>
|
||||
var TOTAL_EXAMPLES = 0;
|
||||
function example() {
|
||||
var scripts = document.getElementsByTagName('script');
|
||||
var last = scripts[scripts.length - 2];
|
||||
last.className = 'Example' + (++TOTAL_EXAMPLES);
|
||||
}
|
||||
</script>
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
|
||||
var HorizontalPan = function(anim, config) {
|
||||
config = config || {};
|
||||
return {
|
||||
onMouseDown: function(event) {
|
||||
anim.stopAnimation(startValue => {
|
||||
config.onStart && config.onStart();
|
||||
var startPosition = event.clientX;
|
||||
var lastTime = Date.now();
|
||||
var lastPosition = event.clientX;
|
||||
var velocity = 0;
|
||||
|
||||
function updateVelocity(event) {
|
||||
var now = Date.now();
|
||||
if (event.clientX === lastPosition || now === lastTime) {
|
||||
return;
|
||||
}
|
||||
velocity = (event.clientX - lastPosition) / (now - lastTime);
|
||||
lastTime = now;
|
||||
lastPosition = event.clientX;
|
||||
}
|
||||
|
||||
var moveListener, upListener;
|
||||
window.addEventListener('mousemove', moveListener = (event) => {
|
||||
var value = startValue + (event.clientX - startPosition);
|
||||
anim.setValue(value);
|
||||
updateVelocity(event);
|
||||
});
|
||||
window.addEventListener('mouseup', upListener = (event) => {
|
||||
updateVelocity(event);
|
||||
window.removeEventListener('mousemove', moveListener);
|
||||
window.removeEventListener('mouseup', upListener);
|
||||
config.onEnd && config.onEnd({velocity});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var EXAMPLE_COUNT = 0;
|
||||
function examplify(Component) {
|
||||
if (EXAMPLE_COUNT) {
|
||||
var previousScript = document.getElementsByClassName('Example' + EXAMPLE_COUNT)[0].innerText;
|
||||
} else {
|
||||
var previousScript = `
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(0), // ignore
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div // ignore
|
||||
style={{left: this.state.anim}} // ignore
|
||||
className="circle"
|
||||
/>
|
||||
);
|
||||
},
|
||||
}));
|
||||
`;
|
||||
}
|
||||
|
||||
var script = document.getElementsByClassName('Example' + ++EXAMPLE_COUNT)[0];
|
||||
|
||||
var previousLines = {};
|
||||
previousScript.split(/\n/g).forEach(line => { previousLines[line.trim()] = 1; });
|
||||
var code = document.createElement('div');
|
||||
script.parentNode.insertBefore(code, script);
|
||||
|
||||
var Example = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<div className="code">
|
||||
<div className="example">
|
||||
<button
|
||||
className="reset"
|
||||
onClick={() => this.forceUpdate()}>
|
||||
Reset
|
||||
</button>
|
||||
<Component key={Math.random()} />
|
||||
</div>
|
||||
<hr />
|
||||
<pre>{'React.createClass({'}</pre>
|
||||
{script.innerText
|
||||
.trim()
|
||||
.split(/\n/g)
|
||||
.slice(1, -1)
|
||||
.map(line =>
|
||||
<pre
|
||||
className={!previousLines[line.trim()] && 'highlight'}>
|
||||
{line}
|
||||
</pre>
|
||||
)
|
||||
}
|
||||
<pre>{'});'}</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
})
|
||||
|
||||
React.render(<Example />, code);
|
||||
}
|
||||
</script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="container">
|
||||
|
||||
<h1>Animated</h1>
|
||||
<p>Animations have for a long time been a weak point of the React ecosystem. The <code>Animated</code> library aims at solving this problem. It embraces the declarative aspect of React and obtains performance by using raw DOM manipulation behind the scenes instead of the usual diff.</p>
|
||||
|
||||
<h2>Animated.Value</h2>
|
||||
|
||||
<p>The basic building block of this library is <code>Animated.Value</code>. This is a variable that's going to drive the animation. You use it like a normal value in <code>style</code> attribute. Only animated components such as <code>Animated.div</code> will understand it.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(100),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div
|
||||
style={{left: this.state.anim}}
|
||||
className="circle"
|
||||
/>
|
||||
);
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<h2>setValue</h2>
|
||||
|
||||
<p>As you can see, the value is being used inside of <code>render()</code> as you would expect. However, you don't call <code>setState()</code> in order to update the value. Instead, you can call <code>setValue()</code> directly on the value itself. We are using a form of data binding.</p>
|
||||
|
||||
<p>The <code>Animated.div</code> component when rendered tracks which animated values it received. This way, whenever that value changes, we don't need to re-render the entire component, we can directly update the specific style attribute that changed.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(0),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div
|
||||
style={{left: this.state.anim}}
|
||||
className="circle"
|
||||
onClick={this.handleClick}>
|
||||
Click
|
||||
</Animated.div>
|
||||
);
|
||||
},
|
||||
handleClick: function() {
|
||||
this.state.anim.setValue(400);
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<h2>Animated.timing</h2>
|
||||
|
||||
<p>Now that we understand how the system works, let's play with some animations! The hello world of animations is to move the element somewhere else. To do that, we're going to animate the value from the current value 0 to the value 400.</p>
|
||||
|
||||
<p>On every frame (via <code>requestAnimationFrame</code>), the <code>timing</code> animation is going to figure out the new value based on the current time, update the animated value which in turn is going to update the corresponding DOM node.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(0),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div
|
||||
style={{left: this.state.anim}}
|
||||
className="circle"
|
||||
onClick={this.handleClick}>
|
||||
Click
|
||||
</Animated.div>
|
||||
);
|
||||
},
|
||||
handleClick: function() {
|
||||
Animated.timing(this.state.anim, {toValue: 400}).start();
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<h2>Interrupt Animations</h2>
|
||||
|
||||
<p>As a developer, the mental model is that most animations are fire and forget. When the user presses the button, you want it to shrink to 80% and when she releases, you want it to go back to 100%.</p>
|
||||
|
||||
<p>There are multiple challenges to implement this correctly. You need to stop the current animation, grab the current value and restart an animation from there. As this is pretty tedious to do manually, <code>Animated</code> will do that automatically for you.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(1),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div
|
||||
style={{transform: [{scale: this.state.anim}]}}
|
||||
className="circle"
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onMouseUp={this.handleMouseUp}>
|
||||
Press
|
||||
</Animated.div>
|
||||
);
|
||||
},
|
||||
handleMouseDown: function() {
|
||||
Animated.timing(this.state.anim, { toValue: 0.8 }).start();
|
||||
},
|
||||
handleMouseUp: function() {
|
||||
Animated.timing(this.state.anim, { toValue: 1 }).start();
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<h2>Animated.spring</h2>
|
||||
|
||||
<p>Unfortunately, the <code>timing</code> animation doesn't feel good. The main reason is that no matter how far you are in the animation, it will trigger a new one with always the same duration.</p>
|
||||
|
||||
<p>The commonly used solution for this problem is to use the equation of a real-world spring. Imagine that you attach a spring to the target value, stretch it to the current value and let it go. The spring movement is going to be the same as the update.</p>
|
||||
|
||||
<p>It turns out that this model is useful in a very wide range of animations. I highly recommend you to always start with a <code>spring</code> animation instead of a <code>timing</code> animation. It will make your interface feels much better.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(1),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div
|
||||
style={{transform: [{scale: this.state.anim}]}}
|
||||
className="circle"
|
||||
onMouseDown={this.handleMouseDown}
|
||||
onMouseUp={this.handleMouseUp}>
|
||||
Press
|
||||
</Animated.div>
|
||||
);
|
||||
},
|
||||
handleMouseDown: function() {
|
||||
Animated.spring(this.state.anim, { toValue: 0.8 }).start();
|
||||
},
|
||||
handleMouseUp: function() {
|
||||
Animated.spring(this.state.anim, { toValue: 1 }).start();
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<h2>interpolate</h2>
|
||||
|
||||
<p>It is very common to animate multiple attributes during the same animation. The usual way to implement it is to start a separate animation for each of the attribute. The downside is that you now have to manage a different state per attribute which is not ideal.</p>
|
||||
|
||||
<p>With <code>Animated</code>, you can use a single state variable and render it in multiple attributes. When the value is updated, all the places will reflect the change.</p>
|
||||
|
||||
<p>In the following example, we're going to model the animation with a variable where 1 means fully visible and 0 means fully hidden. We can pass it directly to the scale attribute as the ranges match. But for the rotation, we need to convert [0 ; 1] range to [260deg ; 0deg]. This is where <code>interpolate()</code> comes handy.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(1),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div
|
||||
style={{
|
||||
transform: [
|
||||
{rotate: this.state.anim.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['260deg', '0deg']
|
||||
})},
|
||||
{scale: this.state.anim},
|
||||
]
|
||||
}}
|
||||
className="circle"
|
||||
onClick={this.handleClick}>
|
||||
Click
|
||||
</Animated.div>
|
||||
);
|
||||
},
|
||||
handleClick: function() {
|
||||
Animated.spring(this.state.anim, {toValue: 0}).start();
|
||||
}
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<h2>stopAnimation</h2>
|
||||
|
||||
<p>The reason why we can get away with not calling <code>render()</code> and instead modify the DOM directly on updates is because the animated values are <strong>opaque</strong>. In render, <strong>you cannot know the current value</strong>, which prevents you from being able to modify the structure of the DOM.</p>
|
||||
|
||||
<p><code>Animated</code> can offload the animation to a different thread (CoreAnimation, CSS transitions, main thread...) and we don't have a good way to know the real value. If you try to query the value then modify it, you are going to be out of sync and the result will look terrible.</p>
|
||||
|
||||
<p>There's however one exception: when you want to stop the current animation. You need to know where it stopped in order to continue from there. We cannot know the value synchronously so we give it via a callback in <code>stopAnimation</code>. It will not suffer from being out of sync since the animation is no longer running.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(0)
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
<button onClick={() => this.handleClick(-1)}><</button>
|
||||
<Animated.div
|
||||
style={{
|
||||
transform: [
|
||||
{rotate: this.state.anim.interpolate({
|
||||
inputRange: [0, 4],
|
||||
outputRange: ['0deg', '360deg']
|
||||
})},
|
||||
],
|
||||
position: 'relative'
|
||||
}}
|
||||
className="circle"
|
||||
/>
|
||||
<button onClick={() => this.handleClick(+1)}>></button>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
handleClick: function(delta) {
|
||||
this.state.anim.stopAnimation(value => {
|
||||
Animated.spring(this.state.anim, {
|
||||
toValue: Math.round(value) + delta
|
||||
}).start();
|
||||
});
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
|
||||
<h1>Gesture-based Animations</h1>
|
||||
|
||||
<p>Most animations libraries only deal with time-based animations. But, as we move to mobile, a lot of animations are also gesture driven. Even more problematic, they often switch between both modes: once the gesture is over, you start a time-based animation using the same interpolations.</p>
|
||||
|
||||
<p><code>Animated</code> has been designed with this use case in mind. The key aspect is that there are three distinct and separate concepts: inputs, value, output. The same value can be updated either from a time-based animation or a gesture-based one. Because we use this intermediate representation for the animation, we can keep the same rendering as output.</p>
|
||||
|
||||
<h2>HorizontalPan</h2>
|
||||
|
||||
<p>The code needed to drag elements around is very messy with the DOM APIs. On mousedown, you need to register a mousemove listener on window otherwise you may drop touches if you move too fast. <code>removeEventListener</code> takes the same arguments as <code>addEventListener</code> instead of an id like <code>clearTimeout</code>. It's also really easy to forget to remove a listener and have a leak. And finally, you need to store the current position and value at the beginning and update only compared to it.</p>
|
||||
|
||||
<p>We introduce a little helper called <code>HorizontalPan</code> which handles all this annoying code for us. It takes an <code>Animated.Value</code> as first argument and returns the event handlers required for it to work. We just have to bind this value to the <code>left</code> attribute and we're good to go.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(0),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div
|
||||
style={{left: this.state.anim}}
|
||||
className="circle"
|
||||
{...HorizontalPan(this.state.anim)}>
|
||||
Drag
|
||||
</Animated.div>
|
||||
);
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<h2>Animated.decay</h2>
|
||||
|
||||
<p>One of the big breakthrough of the iPhone is the fact that when you release the finger while scrolling, it will not abruptly stop but instead keep going for some time.</p>
|
||||
|
||||
<p>In order to implement this effect, we are using a second real-world simulation: an object moving on an icy surface. All it needs is two values: the current velocity and a deceleration coefficient. It is implemented by <code>Animated.decay</code>.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(0),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div
|
||||
style={{left: this.state.anim}}
|
||||
className="circle"
|
||||
{...HorizontalPan(this.state.anim, {
|
||||
onEnd: this.handleEnd
|
||||
})}>
|
||||
Throw
|
||||
</Animated.div>
|
||||
);
|
||||
},
|
||||
handleEnd: function({velocity}) {
|
||||
Animated.decay(this.state.anim, {velocity}).start();
|
||||
}
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<h2>Animation Chaining</h2>
|
||||
|
||||
<p>The target for an animation is usually a number but sometimes it is convenient to use another value as a target. This way, the first value will track the second. Using a spring animation, we can get a nice trailing effect.</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
var anims = [0, 1, 2, 3, 4].map((_, i) => new Animated.Value(0));
|
||||
Animated.spring(anims[0], {toValue: anims[1]}).start();
|
||||
Animated.spring(anims[1], {toValue: anims[2]}).start();
|
||||
Animated.spring(anims[2], {toValue: anims[3]}).start();
|
||||
Animated.spring(anims[3], {toValue: anims[4]}).start();
|
||||
return {
|
||||
anims: anims,
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
{this.state.anims.map((anim, i) =>
|
||||
<Animated.div
|
||||
style={{left: anim}}
|
||||
className="circle"
|
||||
{...(i === 4 && HorizontalPan(anim, {
|
||||
onEnd: this.handleEnd
|
||||
}))}>
|
||||
{i === 4 && 'Drag'}
|
||||
</Animated.div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
handleEnd: function({velocity}) {
|
||||
Animated.decay(this.state.anims[4], {velocity}).start();
|
||||
}
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<h2>addListener</h2>
|
||||
|
||||
<p>As I said earlier, if you track a spring</p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
var anims = [0, 1, 2, 3, 4].map((_, i) => new Animated.Value(i * 100));
|
||||
anims[0].addListener(this.handleChange);
|
||||
return {
|
||||
selected: null,
|
||||
anims: anims,
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
{this.state.anims.map((anim, i) =>
|
||||
<Animated.div
|
||||
style={{
|
||||
left: anim,
|
||||
opacity: i === 0 || i === this.state.selected ? 1 : 0.5
|
||||
}}
|
||||
className="circle"
|
||||
{...(i === 0 && HorizontalPan(anim, { onEnd: this.handleEnd }))}>
|
||||
{i === 0 && 'Drag'}
|
||||
{i === this.state.selected && 'Selected!'}
|
||||
</Animated.div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
handleChange: function({value}) {
|
||||
var selected = null;
|
||||
this.state.anims.forEach((_, i) => {
|
||||
if (i !== 0 && i * 100 - 50 < value && value <= i * 100 + 50) {
|
||||
selected = i;
|
||||
}
|
||||
});
|
||||
if (selected !== this.state.selected) {
|
||||
this.select(selected)
|
||||
}
|
||||
},
|
||||
select(selected) {
|
||||
this.setState({selected}, () => {
|
||||
this.state.anims.forEach((anim, i) => {
|
||||
if (i === 0) { return; }
|
||||
if (selected === i) {
|
||||
Animated.spring(anim, {toValue: this.state.anims[0]}).start();
|
||||
} else {
|
||||
Animated.spring(anim, {toValue: i * 100}).start();
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
handleEnd() {
|
||||
this.select(null);
|
||||
Animated.spring(this.state.anims[0], {toValue: 0}).start();
|
||||
}
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
|
||||
<h2>Animated.sequence</h2>
|
||||
|
||||
<p>It is very common to animate </p>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anims: [0, 1, 2, 3, 4].map((_, i) => new Animated.Value(0.2)),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
{this.state.anims.map((anim, i) =>
|
||||
<Animated.div
|
||||
style={{opacity: anim, position: 'relative'}}
|
||||
className="circle"
|
||||
onClick={i === 0 && this.handleClick}>
|
||||
{i === 0 && 'Click'}
|
||||
</Animated.div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
handleClick: function() {
|
||||
this.state.anims.forEach(anim => { anim.setValue(0.2); });
|
||||
Animated.sequence(
|
||||
this.state.anims.map(anim => Animated.spring(anim, { toValue: 1 }))
|
||||
).start();
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anims: [0, 1, 2, 3, 4].map((_, i) => new Animated.Value(0.2)),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
{this.state.anims.map((anim, i) =>
|
||||
<Animated.div
|
||||
style={{opacity: anim, position: 'relative'}}
|
||||
className="circle"
|
||||
onClick={i === 0 && this.handleClick}>
|
||||
{i === 0 && 'Click'}
|
||||
</Animated.div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
handleClick: function() {
|
||||
this.state.anims.forEach(anim => { anim.setValue(0.2); });
|
||||
Animated.stagger(
|
||||
100,
|
||||
this.state.anims.map(anim => Animated.spring(anim, { toValue: 1 }))
|
||||
).start();
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anims: [0, 1, 2, 3, 4].map((_, i) => new Animated.Value(0.2)),
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div>
|
||||
{this.state.anims.map((anim, i) =>
|
||||
<Animated.div
|
||||
style={{opacity: anim, position: 'relative'}}
|
||||
className="circle"
|
||||
onClick={i === 0 && this.handleClick}>
|
||||
{i === 0 && 'Click'}
|
||||
</Animated.div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
handleClick: function() {
|
||||
this.state.anims.forEach(anim => { anim.setValue(0.2); });
|
||||
Animated.sequence([
|
||||
Animated.parallel(
|
||||
this.state.anims.map(anim => Animated.spring(anim, { toValue: 1 }))
|
||||
),
|
||||
Animated.stagger(
|
||||
100,
|
||||
this.state.anims.map(anim => Animated.spring(anim, { toValue: 0.2 }))
|
||||
),
|
||||
]).start();
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(0),
|
||||
};
|
||||
},
|
||||
handleClick: function() {
|
||||
var rec = () => {
|
||||
Animated.sequence([
|
||||
Animated.timing(this.state.anim, {toValue: -1, duration: 150}),
|
||||
Animated.timing(this.state.anim, {toValue: 1, duration: 150}),
|
||||
]).start(rec);
|
||||
};
|
||||
rec();
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<Animated.div
|
||||
style={{
|
||||
left: this.state.anim.interpolate({
|
||||
inputRange: [-1, -0.5, 0.5, 1],
|
||||
outputRange: [0, 5, 0, 5]
|
||||
}),
|
||||
transform: [
|
||||
{rotate: this.state.anim.interpolate({
|
||||
inputRange: [-1, 1],
|
||||
outputRange: ['-10deg', '10deg']
|
||||
})}
|
||||
]
|
||||
}}
|
||||
className="circle"
|
||||
onClick={this.handleClick}>
|
||||
Click
|
||||
</Animated.div>
|
||||
);
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
<script type="text/jsx;harmony=true;stripTypes=true">
|
||||
examplify(React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
anim: new Animated.Value(0)
|
||||
};
|
||||
},
|
||||
render: function() {
|
||||
return (
|
||||
<div
|
||||
style={{overflow: 'scroll', height: 60}}
|
||||
onScroll={Animated.event([
|
||||
{target: {scrollLeft: this.state.anim}}
|
||||
])}>
|
||||
<div style={{width: 1000, height: 1}} />
|
||||
{[0, 1, 2, 3, 4].map(i =>
|
||||
<Animated.div
|
||||
style={{
|
||||
left: this.state.anim.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, (i + 1)]
|
||||
}),
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
className="circle">
|
||||
{i === 4 && 'H-Scroll'}
|
||||
</Animated.div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
}));
|
||||
</script><script>example();</script>
|
||||
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB |
@@ -1,81 +0,0 @@
|
||||
html, h1, h2 {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.container {
|
||||
width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.circle {
|
||||
margin: 2px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
position: absolute;
|
||||
display: inline-block;
|
||||
box-shadow: 0 1px 2px #999;
|
||||
text-shadow: 0 1px 2px #999;
|
||||
background-image: url(pic1.jpg);
|
||||
background-size: cover;
|
||||
line-height: 80px;
|
||||
vertical-align: bottom;
|
||||
text-align: center;
|
||||
color: white;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.circle:nth-child(2) {
|
||||
background-image: url(pic2.jpg);
|
||||
}
|
||||
|
||||
.circle:nth-child(3) {
|
||||
background-image: url(pic3.jpg);
|
||||
}
|
||||
|
||||
div.code {
|
||||
box-shadow: 0 1px 2px #999;
|
||||
width: 600px;
|
||||
padding: 5px;
|
||||
position: relative;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
div.code .reset {
|
||||
float: right;
|
||||
}
|
||||
|
||||
div.code pre {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
hr {
|
||||
border: none;
|
||||
border-bottom: 1px solid #D9D9D9;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.example > span {
|
||||
color: #333;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.example {
|
||||
position: relative;
|
||||
height: 60px;
|
||||
}
|
||||
|
||||
.code pre {
|
||||
margin: 0;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
background: rgb(228, 254, 253);
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
/**
|
||||
* Copyright 2013-2015, 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.
|
||||
* @providesModule gulpfile
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var babel = require('gulp-babel');
|
||||
var babelPluginDEV = require('fbjs-scripts/babel/dev-expression');
|
||||
var babelPluginModules = require('fbjs-scripts/babel/rewrite-modules');
|
||||
var del = require('del');
|
||||
var derequire = require('gulp-derequire');
|
||||
var flatten = require('gulp-flatten');
|
||||
var gulp = require('gulp');
|
||||
var gulpUtil = require('gulp-util');
|
||||
var header = require('gulp-header');
|
||||
var objectAssign = require('object-assign');
|
||||
var runSequence = require('run-sequence');
|
||||
var webpackStream = require('webpack-stream');
|
||||
|
||||
var DEVELOPMENT_HEADER = [
|
||||
'/**',
|
||||
' * Animated v<%= version %>',
|
||||
' */'
|
||||
].join('\n') + '\n';
|
||||
var PRODUCTION_HEADER = [
|
||||
'/**',
|
||||
' * Animated v<%= version %>',
|
||||
' *',
|
||||
' * Copyright 2013-2015, 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.',
|
||||
' *',
|
||||
' */'
|
||||
].join('\n') + '\n';
|
||||
|
||||
var babelOpts = {
|
||||
nonStandard: true,
|
||||
loose: [
|
||||
'es6.classes'
|
||||
],
|
||||
stage: 1,
|
||||
plugins: [babelPluginDEV, babelPluginModules],
|
||||
_moduleMap: objectAssign({}, require('fbjs/module-map'), {
|
||||
'React': 'react',
|
||||
})
|
||||
};
|
||||
|
||||
var buildDist = function(opts) {
|
||||
var webpackOpts = {
|
||||
debug: opts.debug,
|
||||
externals: {
|
||||
'react': 'React',
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
{test: /\.js$/, loader: 'babel'}
|
||||
],
|
||||
},
|
||||
output: {
|
||||
filename: opts.output,
|
||||
library: 'Animated'
|
||||
},
|
||||
plugins: [
|
||||
new webpackStream.webpack.DefinePlugin({
|
||||
'process.env.NODE_ENV': JSON.stringify(
|
||||
opts.debug ? 'development' : 'production'
|
||||
),
|
||||
}),
|
||||
new webpackStream.webpack.optimize.OccurenceOrderPlugin(),
|
||||
new webpackStream.webpack.optimize.DedupePlugin()
|
||||
]
|
||||
};
|
||||
if (!opts.debug) {
|
||||
webpackOpts.plugins.push(
|
||||
new webpackStream.webpack.optimize.UglifyJsPlugin({
|
||||
compress: {
|
||||
hoist_vars: true,
|
||||
screw_ie8: true,
|
||||
warnings: false
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
return webpackStream(webpackOpts, null, function(err, stats) {
|
||||
if (err) {
|
||||
throw new gulpUtil.PluginError('webpack', err);
|
||||
}
|
||||
if (stats.compilation.errors.length) {
|
||||
throw new gulpUtil.PluginError('webpack', stats.toString());
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var paths = {
|
||||
dist: 'dist',
|
||||
entry: 'lib/AnimatedWeb.js',
|
||||
lib: 'lib',
|
||||
src: [
|
||||
'*src/**/*.js',
|
||||
'!src/**/__tests__/**/*.js',
|
||||
'!src/**/__mocks__/**/*.js'
|
||||
],
|
||||
};
|
||||
|
||||
gulp.task('clean', function(cb) {
|
||||
del([paths.dist, paths.lib], cb);
|
||||
});
|
||||
|
||||
gulp.task('modules', function() {
|
||||
return gulp
|
||||
.src(paths.src, {cwd: '../'})
|
||||
.pipe(babel(babelOpts))
|
||||
.pipe(flatten())
|
||||
.pipe(gulp.dest(paths.lib));
|
||||
});
|
||||
|
||||
gulp.task('dist', ['modules'], function () {
|
||||
var distOpts = {
|
||||
debug: true,
|
||||
output: 'animated.js'
|
||||
};
|
||||
return gulp
|
||||
.src(paths.entry)
|
||||
.pipe(buildDist(distOpts))
|
||||
.pipe(derequire())
|
||||
.pipe(header(DEVELOPMENT_HEADER, {
|
||||
version: process.env.npm_package_version
|
||||
}))
|
||||
.pipe(gulp.dest(paths.dist));
|
||||
});
|
||||
|
||||
gulp.task('dist:min', ['modules'], function () {
|
||||
var distOpts = {
|
||||
debug: false,
|
||||
output: 'animated.min.js'
|
||||
};
|
||||
return gulp
|
||||
.src(paths.entry)
|
||||
.pipe(buildDist(distOpts))
|
||||
.pipe(header(PRODUCTION_HEADER, {
|
||||
version: process.env.npm_package_version
|
||||
}))
|
||||
.pipe(gulp.dest(paths.dist));
|
||||
});
|
||||
|
||||
gulp.task('watch', function() {
|
||||
gulp.watch(paths.src, ['modules']);
|
||||
});
|
||||
|
||||
gulp.task('default', function(cb) {
|
||||
runSequence('clean', 'modules', ['dist', 'dist:min'], cb);
|
||||
});
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"name": "react-animated",
|
||||
"description": "Animated provides powerful mechanisms for animating your React views",
|
||||
"version": "0.1.0",
|
||||
"keywords": [
|
||||
"react",
|
||||
"animated",
|
||||
"animation"
|
||||
],
|
||||
"license": "BSD-3-Clause",
|
||||
"main": "Animated.js",
|
||||
"dependencies": {
|
||||
"fbjs": "^0.2.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-core": "^5.8.25",
|
||||
"babel-loader": "^5.3.2",
|
||||
"del": "^1.2.0",
|
||||
"fbjs-scripts": "^0.2.0",
|
||||
"gulp": "^3.9.0",
|
||||
"gulp-babel": "^5.1.0",
|
||||
"gulp-derequire": "^2.1.0",
|
||||
"gulp-flatten": "^0.1.0",
|
||||
"gulp-header": "^1.2.2",
|
||||
"gulp-util": "^3.0.6",
|
||||
"object-assign": "^3.0.0",
|
||||
"run-sequence": "^1.1.2",
|
||||
"webpack": "1.11.0",
|
||||
"webpack-stream": "^2.1.0"
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule Animated
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
|
||||
var AnimatedImplementation = require('AnimatedImplementation');
|
||||
var Image = require('Image');
|
||||
var Text = require('Text');
|
||||
var View = require('View');
|
||||
|
||||
let AnimatedScrollView;
|
||||
|
||||
const Animated = {
|
||||
View: AnimatedImplementation.createAnimatedComponent(View),
|
||||
Text: AnimatedImplementation.createAnimatedComponent(Text),
|
||||
Image: AnimatedImplementation.createAnimatedComponent(Image),
|
||||
get ScrollView() {
|
||||
// Make this lazy to avoid circular reference.
|
||||
if (!AnimatedScrollView) {
|
||||
AnimatedScrollView = AnimatedImplementation.createAnimatedComponent(require('ScrollView'));
|
||||
}
|
||||
return AnimatedScrollView;
|
||||
},
|
||||
};
|
||||
|
||||
Object.assign((Animated: Object), AnimatedImplementation);
|
||||
|
||||
module.exports = ((Animated: any): (typeof AnimatedImplementation) & typeof Animated);
|
||||
@@ -1,196 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AnimatedEvent
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedValue = require('./nodes/AnimatedValue');
|
||||
const NativeAnimatedHelper = require('./NativeAnimatedHelper');
|
||||
const ReactNative = require('ReactNative');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const {shouldUseNativeDriver} = require('./NativeAnimatedHelper');
|
||||
|
||||
export type Mapping = {[key: string]: Mapping} | AnimatedValue;
|
||||
export type EventConfig = {
|
||||
listener?: ?Function,
|
||||
useNativeDriver?: boolean,
|
||||
};
|
||||
|
||||
function attachNativeEvent(
|
||||
viewRef: any,
|
||||
eventName: string,
|
||||
argMapping: Array<?Mapping>,
|
||||
) {
|
||||
// Find animated values in `argMapping` and create an array representing their
|
||||
// key path inside the `nativeEvent` object. Ex.: ['contentOffset', 'x'].
|
||||
const eventMappings = [];
|
||||
|
||||
const traverse = (value, path) => {
|
||||
if (value instanceof AnimatedValue) {
|
||||
value.__makeNative();
|
||||
|
||||
eventMappings.push({
|
||||
nativeEventPath: path,
|
||||
animatedValueTag: value.__getNativeTag(),
|
||||
});
|
||||
} else if (typeof value === 'object') {
|
||||
for (const key in value) {
|
||||
traverse(value[key], path.concat(key));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
invariant(
|
||||
argMapping[0] && argMapping[0].nativeEvent,
|
||||
'Native driven events only support animated values contained inside `nativeEvent`.',
|
||||
);
|
||||
|
||||
// Assume that the event containing `nativeEvent` is always the first argument.
|
||||
traverse(argMapping[0].nativeEvent, []);
|
||||
|
||||
const viewTag = ReactNative.findNodeHandle(viewRef);
|
||||
|
||||
eventMappings.forEach(mapping => {
|
||||
NativeAnimatedHelper.API.addAnimatedEventToView(
|
||||
viewTag,
|
||||
eventName,
|
||||
mapping,
|
||||
);
|
||||
});
|
||||
|
||||
return {
|
||||
detach() {
|
||||
eventMappings.forEach(mapping => {
|
||||
NativeAnimatedHelper.API.removeAnimatedEventFromView(
|
||||
viewTag,
|
||||
eventName,
|
||||
mapping.animatedValueTag,
|
||||
);
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
class AnimatedEvent {
|
||||
_argMapping: Array<?Mapping>;
|
||||
_listeners: Array<Function> = [];
|
||||
_callListeners: Function;
|
||||
_attachedEvent: ?{
|
||||
detach: () => void,
|
||||
};
|
||||
__isNative: boolean;
|
||||
|
||||
constructor(argMapping: Array<?Mapping>, config?: EventConfig = {}) {
|
||||
this._argMapping = argMapping;
|
||||
if (config.listener) {
|
||||
this.__addListener(config.listener);
|
||||
}
|
||||
this._callListeners = this._callListeners.bind(this);
|
||||
this._attachedEvent = null;
|
||||
this.__isNative = shouldUseNativeDriver(config);
|
||||
|
||||
if (__DEV__) {
|
||||
this._validateMapping();
|
||||
}
|
||||
}
|
||||
|
||||
__addListener(callback: Function): void {
|
||||
this._listeners.push(callback);
|
||||
}
|
||||
|
||||
__removeListener(callback: Function): void {
|
||||
this._listeners = this._listeners.filter(listener => listener !== callback);
|
||||
}
|
||||
|
||||
__attach(viewRef: any, eventName: string) {
|
||||
invariant(
|
||||
this.__isNative,
|
||||
'Only native driven events need to be attached.',
|
||||
);
|
||||
|
||||
this._attachedEvent = attachNativeEvent(
|
||||
viewRef,
|
||||
eventName,
|
||||
this._argMapping,
|
||||
);
|
||||
}
|
||||
|
||||
__detach(viewTag: any, eventName: string) {
|
||||
invariant(
|
||||
this.__isNative,
|
||||
'Only native driven events need to be detached.',
|
||||
);
|
||||
|
||||
this._attachedEvent && this._attachedEvent.detach();
|
||||
}
|
||||
|
||||
__getHandler() {
|
||||
if (this.__isNative) {
|
||||
return this._callListeners;
|
||||
}
|
||||
|
||||
return (...args: any) => {
|
||||
const traverse = (recMapping, recEvt, key) => {
|
||||
if (typeof recEvt === 'number' && recMapping instanceof AnimatedValue) {
|
||||
recMapping.setValue(recEvt);
|
||||
} else if (typeof recMapping === 'object') {
|
||||
for (const mappingKey in recMapping) {
|
||||
/* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This
|
||||
* comment suppresses an error when upgrading Flow's support for
|
||||
* React. To see the error delete this comment and run Flow. */
|
||||
traverse(recMapping[mappingKey], recEvt[mappingKey], mappingKey);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (!this.__isNative) {
|
||||
this._argMapping.forEach((mapping, idx) => {
|
||||
traverse(mapping, args[idx], 'arg' + idx);
|
||||
});
|
||||
}
|
||||
this._callListeners(...args);
|
||||
};
|
||||
}
|
||||
|
||||
_callListeners(...args) {
|
||||
this._listeners.forEach(listener => listener(...args));
|
||||
}
|
||||
|
||||
_validateMapping() {
|
||||
const traverse = (recMapping, recEvt, key) => {
|
||||
if (typeof recEvt === 'number') {
|
||||
invariant(
|
||||
recMapping instanceof AnimatedValue,
|
||||
'Bad mapping of type ' +
|
||||
typeof recMapping +
|
||||
' for key ' +
|
||||
key +
|
||||
', event value must map to AnimatedValue',
|
||||
);
|
||||
return;
|
||||
}
|
||||
invariant(
|
||||
typeof recMapping === 'object',
|
||||
'Bad mapping of type ' + typeof recMapping + ' for key ' + key,
|
||||
);
|
||||
invariant(
|
||||
typeof recEvt === 'object',
|
||||
'Bad event of type ' + typeof recEvt + ' for key ' + key,
|
||||
);
|
||||
for (const mappingKey in recMapping) {
|
||||
traverse(recMapping[mappingKey], recEvt[mappingKey], mappingKey);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {AnimatedEvent, attachNativeEvent};
|
||||
@@ -1,840 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AnimatedImplementation
|
||||
* @flow
|
||||
* @format
|
||||
* @preventMunge
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const {AnimatedEvent, attachNativeEvent} = require('./AnimatedEvent');
|
||||
const AnimatedAddition = require('./nodes/AnimatedAddition');
|
||||
const AnimatedDiffClamp = require('./nodes/AnimatedDiffClamp');
|
||||
const AnimatedDivision = require('./nodes/AnimatedDivision');
|
||||
const AnimatedInterpolation = require('./nodes/AnimatedInterpolation');
|
||||
const AnimatedModulo = require('./nodes/AnimatedModulo');
|
||||
const AnimatedMultiplication = require('./nodes/AnimatedMultiplication');
|
||||
const AnimatedNode = require('./nodes/AnimatedNode');
|
||||
const AnimatedProps = require('./nodes/AnimatedProps');
|
||||
const AnimatedTracking = require('./nodes/AnimatedTracking');
|
||||
const AnimatedValue = require('./nodes/AnimatedValue');
|
||||
const AnimatedValueXY = require('./nodes/AnimatedValueXY');
|
||||
const DecayAnimation = require('./animations/DecayAnimation');
|
||||
const SpringAnimation = require('./animations/SpringAnimation');
|
||||
const TimingAnimation = require('./animations/TimingAnimation');
|
||||
|
||||
const createAnimatedComponent = require('./createAnimatedComponent');
|
||||
|
||||
import type {
|
||||
AnimationConfig,
|
||||
EndCallback,
|
||||
EndResult,
|
||||
} from './animations/Animation';
|
||||
import type {TimingAnimationConfig} from './animations/TimingAnimation';
|
||||
import type {DecayAnimationConfig} from './animations/DecayAnimation';
|
||||
import type {SpringAnimationConfig} from './animations/SpringAnimation';
|
||||
import type {Mapping, EventConfig} from './AnimatedEvent';
|
||||
|
||||
type CompositeAnimation = {
|
||||
start: (callback?: ?EndCallback) => void,
|
||||
stop: () => void,
|
||||
reset: () => void,
|
||||
_startNativeLoop: (iterations?: number) => void,
|
||||
_isUsingNativeDriver: () => boolean,
|
||||
};
|
||||
|
||||
const add = function(
|
||||
a: AnimatedNode | number,
|
||||
b: AnimatedNode | number,
|
||||
): AnimatedAddition {
|
||||
return new AnimatedAddition(a, b);
|
||||
};
|
||||
|
||||
const divide = function(
|
||||
a: AnimatedNode | number,
|
||||
b: AnimatedNode | number,
|
||||
): AnimatedDivision {
|
||||
return new AnimatedDivision(a, b);
|
||||
};
|
||||
|
||||
const multiply = function(
|
||||
a: AnimatedNode | number,
|
||||
b: AnimatedNode | number,
|
||||
): AnimatedMultiplication {
|
||||
return new AnimatedMultiplication(a, b);
|
||||
};
|
||||
|
||||
const modulo = function(a: AnimatedNode, modulus: number): AnimatedModulo {
|
||||
return new AnimatedModulo(a, modulus);
|
||||
};
|
||||
|
||||
const diffClamp = function(
|
||||
a: AnimatedNode,
|
||||
min: number,
|
||||
max: number,
|
||||
): AnimatedDiffClamp {
|
||||
return new AnimatedDiffClamp(a, min, max);
|
||||
};
|
||||
|
||||
const _combineCallbacks = function(
|
||||
callback: ?EndCallback,
|
||||
config: AnimationConfig,
|
||||
) {
|
||||
if (callback && config.onComplete) {
|
||||
return (...args) => {
|
||||
config.onComplete && config.onComplete(...args);
|
||||
callback && callback(...args);
|
||||
};
|
||||
} else {
|
||||
return callback || config.onComplete;
|
||||
}
|
||||
};
|
||||
|
||||
const maybeVectorAnim = function(
|
||||
value: AnimatedValue | AnimatedValueXY,
|
||||
config: Object,
|
||||
anim: (value: AnimatedValue, config: Object) => CompositeAnimation,
|
||||
): ?CompositeAnimation {
|
||||
if (value instanceof AnimatedValueXY) {
|
||||
const configX = {...config};
|
||||
const configY = {...config};
|
||||
for (const key in config) {
|
||||
const {x, y} = config[key];
|
||||
if (x !== undefined && y !== undefined) {
|
||||
configX[key] = x;
|
||||
configY[key] = y;
|
||||
}
|
||||
}
|
||||
const aX = anim((value: AnimatedValueXY).x, configX);
|
||||
const aY = anim((value: AnimatedValueXY).y, configY);
|
||||
// We use `stopTogether: false` here because otherwise tracking will break
|
||||
// because the second animation will get stopped before it can update.
|
||||
return parallel([aX, aY], {stopTogether: false});
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const spring = function(
|
||||
value: AnimatedValue | AnimatedValueXY,
|
||||
config: SpringAnimationConfig,
|
||||
): CompositeAnimation {
|
||||
const start = function(
|
||||
animatedValue: AnimatedValue | AnimatedValueXY,
|
||||
configuration: SpringAnimationConfig,
|
||||
callback?: ?EndCallback,
|
||||
): void {
|
||||
callback = _combineCallbacks(callback, configuration);
|
||||
const singleValue: any = animatedValue;
|
||||
const singleConfig: any = configuration;
|
||||
singleValue.stopTracking();
|
||||
if (configuration.toValue instanceof AnimatedNode) {
|
||||
singleValue.track(
|
||||
new AnimatedTracking(
|
||||
singleValue,
|
||||
configuration.toValue,
|
||||
SpringAnimation,
|
||||
singleConfig,
|
||||
callback,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
singleValue.animate(new SpringAnimation(singleConfig), callback);
|
||||
}
|
||||
};
|
||||
return (
|
||||
maybeVectorAnim(value, config, spring) || {
|
||||
start: function(callback?: ?EndCallback): void {
|
||||
start(value, config, callback);
|
||||
},
|
||||
|
||||
stop: function(): void {
|
||||
value.stopAnimation();
|
||||
},
|
||||
|
||||
reset: function(): void {
|
||||
value.resetAnimation();
|
||||
},
|
||||
|
||||
_startNativeLoop: function(iterations?: number): void {
|
||||
const singleConfig = {...config, iterations};
|
||||
start(value, singleConfig);
|
||||
},
|
||||
|
||||
_isUsingNativeDriver: function(): boolean {
|
||||
return config.useNativeDriver || false;
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const timing = function(
|
||||
value: AnimatedValue | AnimatedValueXY,
|
||||
config: TimingAnimationConfig,
|
||||
): CompositeAnimation {
|
||||
const start = function(
|
||||
animatedValue: AnimatedValue | AnimatedValueXY,
|
||||
configuration: TimingAnimationConfig,
|
||||
callback?: ?EndCallback,
|
||||
): void {
|
||||
callback = _combineCallbacks(callback, configuration);
|
||||
const singleValue: any = animatedValue;
|
||||
const singleConfig: any = configuration;
|
||||
singleValue.stopTracking();
|
||||
if (configuration.toValue instanceof AnimatedNode) {
|
||||
singleValue.track(
|
||||
new AnimatedTracking(
|
||||
singleValue,
|
||||
configuration.toValue,
|
||||
TimingAnimation,
|
||||
singleConfig,
|
||||
callback,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
singleValue.animate(new TimingAnimation(singleConfig), callback);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
maybeVectorAnim(value, config, timing) || {
|
||||
start: function(callback?: ?EndCallback): void {
|
||||
start(value, config, callback);
|
||||
},
|
||||
|
||||
stop: function(): void {
|
||||
value.stopAnimation();
|
||||
},
|
||||
|
||||
reset: function(): void {
|
||||
value.resetAnimation();
|
||||
},
|
||||
|
||||
_startNativeLoop: function(iterations?: number): void {
|
||||
const singleConfig = {...config, iterations};
|
||||
start(value, singleConfig);
|
||||
},
|
||||
|
||||
_isUsingNativeDriver: function(): boolean {
|
||||
return config.useNativeDriver || false;
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const decay = function(
|
||||
value: AnimatedValue | AnimatedValueXY,
|
||||
config: DecayAnimationConfig,
|
||||
): CompositeAnimation {
|
||||
const start = function(
|
||||
animatedValue: AnimatedValue | AnimatedValueXY,
|
||||
configuration: DecayAnimationConfig,
|
||||
callback?: ?EndCallback,
|
||||
): void {
|
||||
callback = _combineCallbacks(callback, configuration);
|
||||
const singleValue: any = animatedValue;
|
||||
const singleConfig: any = configuration;
|
||||
singleValue.stopTracking();
|
||||
singleValue.animate(new DecayAnimation(singleConfig), callback);
|
||||
};
|
||||
|
||||
return (
|
||||
maybeVectorAnim(value, config, decay) || {
|
||||
start: function(callback?: ?EndCallback): void {
|
||||
start(value, config, callback);
|
||||
},
|
||||
|
||||
stop: function(): void {
|
||||
value.stopAnimation();
|
||||
},
|
||||
|
||||
reset: function(): void {
|
||||
value.resetAnimation();
|
||||
},
|
||||
|
||||
_startNativeLoop: function(iterations?: number): void {
|
||||
const singleConfig = {...config, iterations};
|
||||
start(value, singleConfig);
|
||||
},
|
||||
|
||||
_isUsingNativeDriver: function(): boolean {
|
||||
return config.useNativeDriver || false;
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const sequence = function(
|
||||
animations: Array<CompositeAnimation>,
|
||||
): CompositeAnimation {
|
||||
let current = 0;
|
||||
return {
|
||||
start: function(callback?: ?EndCallback) {
|
||||
const onComplete = function(result) {
|
||||
if (!result.finished) {
|
||||
callback && callback(result);
|
||||
return;
|
||||
}
|
||||
|
||||
current++;
|
||||
|
||||
if (current === animations.length) {
|
||||
callback && callback(result);
|
||||
return;
|
||||
}
|
||||
|
||||
animations[current].start(onComplete);
|
||||
};
|
||||
|
||||
if (animations.length === 0) {
|
||||
callback && callback({finished: true});
|
||||
} else {
|
||||
animations[current].start(onComplete);
|
||||
}
|
||||
},
|
||||
|
||||
stop: function() {
|
||||
if (current < animations.length) {
|
||||
animations[current].stop();
|
||||
}
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
animations.forEach((animation, idx) => {
|
||||
if (idx <= current) {
|
||||
animation.reset();
|
||||
}
|
||||
});
|
||||
current = 0;
|
||||
},
|
||||
|
||||
_startNativeLoop: function() {
|
||||
throw new Error(
|
||||
'Loops run using the native driver cannot contain Animated.sequence animations',
|
||||
);
|
||||
},
|
||||
|
||||
_isUsingNativeDriver: function(): boolean {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
type ParallelConfig = {
|
||||
stopTogether?: boolean, // If one is stopped, stop all. default: true
|
||||
};
|
||||
const parallel = function(
|
||||
animations: Array<CompositeAnimation>,
|
||||
config?: ?ParallelConfig,
|
||||
): CompositeAnimation {
|
||||
let doneCount = 0;
|
||||
// Make sure we only call stop() at most once for each animation
|
||||
const hasEnded = {};
|
||||
const stopTogether = !(config && config.stopTogether === false);
|
||||
|
||||
const result = {
|
||||
start: function(callback?: ?EndCallback) {
|
||||
if (doneCount === animations.length) {
|
||||
callback && callback({finished: true});
|
||||
return;
|
||||
}
|
||||
|
||||
animations.forEach((animation, idx) => {
|
||||
const cb = function(endResult) {
|
||||
hasEnded[idx] = true;
|
||||
doneCount++;
|
||||
if (doneCount === animations.length) {
|
||||
doneCount = 0;
|
||||
callback && callback(endResult);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!endResult.finished && stopTogether) {
|
||||
result.stop();
|
||||
}
|
||||
};
|
||||
|
||||
if (!animation) {
|
||||
cb({finished: true});
|
||||
} else {
|
||||
animation.start(cb);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
stop: function(): void {
|
||||
animations.forEach((animation, idx) => {
|
||||
!hasEnded[idx] && animation.stop();
|
||||
hasEnded[idx] = true;
|
||||
});
|
||||
},
|
||||
|
||||
reset: function(): void {
|
||||
animations.forEach((animation, idx) => {
|
||||
animation.reset();
|
||||
hasEnded[idx] = false;
|
||||
doneCount = 0;
|
||||
});
|
||||
},
|
||||
|
||||
_startNativeLoop: function() {
|
||||
throw new Error(
|
||||
'Loops run using the native driver cannot contain Animated.parallel animations',
|
||||
);
|
||||
},
|
||||
|
||||
_isUsingNativeDriver: function(): boolean {
|
||||
return false;
|
||||
},
|
||||
};
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const delay = function(time: number): CompositeAnimation {
|
||||
// Would be nice to make a specialized implementation
|
||||
return timing(new AnimatedValue(0), {toValue: 0, delay: time, duration: 0});
|
||||
};
|
||||
|
||||
const stagger = function(
|
||||
time: number,
|
||||
animations: Array<CompositeAnimation>,
|
||||
): CompositeAnimation {
|
||||
return parallel(
|
||||
animations.map((animation, i) => {
|
||||
return sequence([delay(time * i), animation]);
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
type LoopAnimationConfig = {iterations: number};
|
||||
|
||||
const loop = function(
|
||||
animation: CompositeAnimation,
|
||||
{iterations = -1}: LoopAnimationConfig = {},
|
||||
): CompositeAnimation {
|
||||
let isFinished = false;
|
||||
let iterationsSoFar = 0;
|
||||
return {
|
||||
start: function(callback?: ?EndCallback) {
|
||||
const restart = function(result: EndResult = {finished: true}): void {
|
||||
if (
|
||||
isFinished ||
|
||||
iterationsSoFar === iterations ||
|
||||
result.finished === false
|
||||
) {
|
||||
callback && callback(result);
|
||||
} else {
|
||||
iterationsSoFar++;
|
||||
animation.reset();
|
||||
animation.start(restart);
|
||||
}
|
||||
};
|
||||
if (!animation || iterations === 0) {
|
||||
callback && callback({finished: true});
|
||||
} else {
|
||||
if (animation._isUsingNativeDriver()) {
|
||||
animation._startNativeLoop(iterations);
|
||||
} else {
|
||||
restart(); // Start looping recursively on the js thread
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
stop: function(): void {
|
||||
isFinished = true;
|
||||
animation.stop();
|
||||
},
|
||||
|
||||
reset: function(): void {
|
||||
iterationsSoFar = 0;
|
||||
isFinished = false;
|
||||
animation.reset();
|
||||
},
|
||||
|
||||
_startNativeLoop: function() {
|
||||
throw new Error(
|
||||
'Loops run using the native driver cannot contain Animated.loop animations',
|
||||
);
|
||||
},
|
||||
|
||||
_isUsingNativeDriver: function(): boolean {
|
||||
return animation._isUsingNativeDriver();
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
function forkEvent(
|
||||
event: ?AnimatedEvent | ?Function,
|
||||
listener: Function,
|
||||
): AnimatedEvent | Function {
|
||||
if (!event) {
|
||||
return listener;
|
||||
} else if (event instanceof AnimatedEvent) {
|
||||
event.__addListener(listener);
|
||||
return event;
|
||||
} else {
|
||||
return (...args) => {
|
||||
typeof event === 'function' && event(...args);
|
||||
listener(...args);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function unforkEvent(
|
||||
event: ?AnimatedEvent | ?Function,
|
||||
listener: Function,
|
||||
): void {
|
||||
if (event && event instanceof AnimatedEvent) {
|
||||
event.__removeListener(listener);
|
||||
}
|
||||
}
|
||||
|
||||
const event = function(argMapping: Array<?Mapping>, config?: EventConfig): any {
|
||||
const animatedEvent = new AnimatedEvent(argMapping, config);
|
||||
if (animatedEvent.__isNative) {
|
||||
return animatedEvent;
|
||||
} else {
|
||||
return animatedEvent.__getHandler();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* The `Animated` library is designed to make animations fluid, powerful, and
|
||||
* easy to build and maintain. `Animated` focuses on declarative relationships
|
||||
* between inputs and outputs, with configurable transforms in between, and
|
||||
* simple `start`/`stop` methods to control time-based animation execution.
|
||||
*
|
||||
* The simplest workflow for creating an animation is to create an
|
||||
* `Animated.Value`, hook it up to one or more style attributes of an animated
|
||||
* component, and then drive updates via animations using `Animated.timing()`:
|
||||
*
|
||||
* ```javascript
|
||||
* Animated.timing( // Animate value over time
|
||||
* this.state.fadeAnim, // The value to drive
|
||||
* {
|
||||
* toValue: 1, // Animate to final value of 1
|
||||
* }
|
||||
* ).start(); // Start the animation
|
||||
* ```
|
||||
*
|
||||
* Refer to the [Animations](docs/animations.html#animated-api) guide to see
|
||||
* additional examples of `Animated` in action.
|
||||
*
|
||||
* ## Overview
|
||||
*
|
||||
* There are two value types you can use with `Animated`:
|
||||
*
|
||||
* - [`Animated.Value()`](docs/animated.html#value) for single values
|
||||
* - [`Animated.ValueXY()`](docs/animated.html#valuexy) for vectors
|
||||
*
|
||||
* `Animated.Value` can bind to style properties or other props, and can be
|
||||
* interpolated as well. A single `Animated.Value` can drive any number of
|
||||
* properties.
|
||||
*
|
||||
* ### Configuring animations
|
||||
*
|
||||
* `Animated` provides three types of animation types. Each animation type
|
||||
* provides a particular animation curve that controls how your values animate
|
||||
* from their initial value to the final value:
|
||||
*
|
||||
* - [`Animated.decay()`](docs/animated.html#decay) starts with an initial
|
||||
* velocity and gradually slows to a stop.
|
||||
* - [`Animated.spring()`](docs/animated.html#spring) provides a simple
|
||||
* spring physics model.
|
||||
* - [`Animated.timing()`](docs/animated.html#timing) animates a value over time
|
||||
* using [easing functions](docs/easing.html).
|
||||
*
|
||||
* In most cases, you will be using `timing()`. By default, it uses a symmetric
|
||||
* easeInOut curve that conveys the gradual acceleration of an object to full
|
||||
* speed and concludes by gradually decelerating to a stop.
|
||||
*
|
||||
* ### Working with animations
|
||||
*
|
||||
* Animations are started by calling `start()` on your animation. `start()`
|
||||
* takes a completion callback that will be called when the animation is done.
|
||||
* If the animation finished running normally, the completion callback will be
|
||||
* invoked with `{finished: true}`. If the animation is done because `stop()`
|
||||
* was called on it before it could finish (e.g. because it was interrupted by a
|
||||
* gesture or another animation), then it will receive `{finished: false}`.
|
||||
*
|
||||
* ### Using the native driver
|
||||
*
|
||||
* By using the native driver, we send everything about the animation to native
|
||||
* before starting the animation, allowing native code to perform the animation
|
||||
* on the UI thread without having to go through the bridge on every frame.
|
||||
* Once the animation has started, the JS thread can be blocked without
|
||||
* affecting the animation.
|
||||
*
|
||||
* You can use the native driver by specifying `useNativeDriver: true` in your
|
||||
* animation configuration. See the
|
||||
* [Animations](docs/animations.html#using-the-native-driver) guide to learn
|
||||
* more.
|
||||
*
|
||||
* ### Animatable components
|
||||
*
|
||||
* Only animatable components can be animated. These special components do the
|
||||
* magic of binding the animated values to the properties, and do targeted
|
||||
* native updates to avoid the cost of the react render and reconciliation
|
||||
* process on every frame. They also handle cleanup on unmount so they are safe
|
||||
* by default.
|
||||
*
|
||||
* - [`createAnimatedComponent()`](docs/animated.html#createanimatedcomponent)
|
||||
* can be used to make a component animatable.
|
||||
*
|
||||
* `Animated` exports the following animatable components using the above
|
||||
* wrapper:
|
||||
*
|
||||
* - `Animated.Image`
|
||||
* - `Animated.ScrollView`
|
||||
* - `Animated.Text`
|
||||
* - `Animated.View`
|
||||
*
|
||||
* ### Composing animations
|
||||
*
|
||||
* Animations can also be combined in complex ways using composition functions:
|
||||
*
|
||||
* - [`Animated.delay()`](docs/animated.html#delay) starts an animation after
|
||||
* a given delay.
|
||||
* - [`Animated.parallel()`](docs/animated.html#parallel) starts a number of
|
||||
* animations at the same time.
|
||||
* - [`Animated.sequence()`](docs/animated.html#sequence) starts the animations
|
||||
* in order, waiting for each to complete before starting the next.
|
||||
* - [`Animated.stagger()`](docs/animated.html#stagger) starts animations in
|
||||
* order and in parallel, but with successive delays.
|
||||
*
|
||||
* Animations can also be chained together simply by setting the `toValue` of
|
||||
* one animation to be another `Animated.Value`. See
|
||||
* [Tracking dynamic values](docs/animations.html#tracking-dynamic-values) in
|
||||
* the Animations guide.
|
||||
*
|
||||
* By default, if one animation is stopped or interrupted, then all other
|
||||
* animations in the group are also stopped.
|
||||
*
|
||||
* ### Combining animated values
|
||||
*
|
||||
* You can combine two animated values via addition, multiplication, division,
|
||||
* or modulo to make a new animated value:
|
||||
*
|
||||
* - [`Animated.add()`](docs/animated.html#add)
|
||||
* - [`Animated.divide()`](docs/animated.html#divide)
|
||||
* - [`Animated.modulo()`](docs/animated.html#modulo)
|
||||
* - [`Animated.multiply()`](docs/animated.html#multiply)
|
||||
*
|
||||
* ### Interpolation
|
||||
*
|
||||
* The `interpolate()` function allows input ranges to map to different output
|
||||
* ranges. By default, it will extrapolate the curve beyond the ranges given,
|
||||
* but you can also have it clamp the output value. It uses lineal interpolation
|
||||
* by default but also supports easing functions.
|
||||
*
|
||||
* - [`interpolate()`](docs/animated.html#interpolate)
|
||||
*
|
||||
* Read more about interpolation in the
|
||||
* [Animation](docs/animations.html#interpolation) guide.
|
||||
*
|
||||
* ### Handling gestures and other events
|
||||
*
|
||||
* Gestures, like panning or scrolling, and other events can map directly to
|
||||
* animated values using `Animated.event()`. This is done with a structured map
|
||||
* syntax so that values can be extracted from complex event objects. The first
|
||||
* level is an array to allow mapping across multiple args, and that array
|
||||
* contains nested objects.
|
||||
*
|
||||
* - [`Animated.event()`](docs/animated.html#event)
|
||||
*
|
||||
* For example, when working with horizontal scrolling gestures, you would do
|
||||
* the following in order to map `event.nativeEvent.contentOffset.x` to
|
||||
* `scrollX` (an `Animated.Value`):
|
||||
*
|
||||
* ```javascript
|
||||
* onScroll={Animated.event(
|
||||
* // scrollX = e.nativeEvent.contentOffset.x
|
||||
* [{ nativeEvent: {
|
||||
* contentOffset: {
|
||||
* x: scrollX
|
||||
* }
|
||||
* }
|
||||
* }]
|
||||
* )}
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
module.exports = {
|
||||
/**
|
||||
* Standard value class for driving animations. Typically initialized with
|
||||
* `new Animated.Value(0);`
|
||||
*
|
||||
* See also [`AnimatedValue`](docs/animated.html#animatedvalue).
|
||||
*/
|
||||
Value: AnimatedValue,
|
||||
/**
|
||||
* 2D value class for driving 2D animations, such as pan gestures.
|
||||
*
|
||||
* See also [`AnimatedValueXY`](docs/animated.html#animatedvaluexy).
|
||||
*/
|
||||
ValueXY: AnimatedValueXY,
|
||||
/**
|
||||
* exported to use the Interpolation type in flow
|
||||
*
|
||||
* See also [`AnimatedInterpolation`](docs/animated.html#animatedinterpolation).
|
||||
*/
|
||||
Interpolation: AnimatedInterpolation,
|
||||
/**
|
||||
* Exported for ease of type checking. All animated values derive from this class.
|
||||
*/
|
||||
Node: AnimatedNode,
|
||||
|
||||
/**
|
||||
* Animates a value from an initial velocity to zero based on a decay
|
||||
* coefficient.
|
||||
*
|
||||
* Config is an object that may have the following options:
|
||||
*
|
||||
* - `velocity`: Initial velocity. Required.
|
||||
* - `deceleration`: Rate of decay. Default 0.997.
|
||||
* - `useNativeDriver`: Uses the native driver when true. Default false.
|
||||
*/
|
||||
decay,
|
||||
/**
|
||||
* Animates a value along a timed easing curve. The
|
||||
* [`Easing`](docs/easing.html) module has tons of predefined curves, or you
|
||||
* can use your own function.
|
||||
*
|
||||
* Config is an object that may have the following options:
|
||||
*
|
||||
* - `duration`: Length of animation (milliseconds). Default 500.
|
||||
* - `easing`: Easing function to define curve.
|
||||
* Default is `Easing.inOut(Easing.ease)`.
|
||||
* - `delay`: Start the animation after delay (milliseconds). Default 0.
|
||||
* - `useNativeDriver`: Uses the native driver when true. Default false.
|
||||
*/
|
||||
timing,
|
||||
/**
|
||||
* Spring animation based on Rebound and
|
||||
* [Origami](https://facebook.github.io/origami/). Tracks velocity state to
|
||||
* create fluid motions as the `toValue` updates, and can be chained together.
|
||||
*
|
||||
* Config is an object that may have the following options. Note that you can
|
||||
* only define bounciness/speed or tension/friction but not both:
|
||||
*
|
||||
* - `friction`: Controls "bounciness"/overshoot. Default 7.
|
||||
* - `tension`: Controls speed. Default 40.
|
||||
* - `speed`: Controls speed of the animation. Default 12.
|
||||
* - `bounciness`: Controls bounciness. Default 8.
|
||||
* - `useNativeDriver`: Uses the native driver when true. Default false.
|
||||
*/
|
||||
spring,
|
||||
|
||||
/**
|
||||
* Creates a new Animated value composed from two Animated values added
|
||||
* together.
|
||||
*/
|
||||
add,
|
||||
|
||||
/**
|
||||
* Creates a new Animated value composed by dividing the first Animated value
|
||||
* by the second Animated value.
|
||||
*/
|
||||
divide,
|
||||
|
||||
/**
|
||||
* Creates a new Animated value composed from two Animated values multiplied
|
||||
* together.
|
||||
*/
|
||||
multiply,
|
||||
|
||||
/**
|
||||
* Creates a new Animated value that is the (non-negative) modulo of the
|
||||
* provided Animated value
|
||||
*/
|
||||
modulo,
|
||||
|
||||
/**
|
||||
* Create a new Animated value that is limited between 2 values. It uses the
|
||||
* difference between the last value so even if the value is far from the bounds
|
||||
* it will start changing when the value starts getting closer again.
|
||||
* (`value = clamp(value + diff, min, max)`).
|
||||
*
|
||||
* This is useful with scroll events, for example, to show the navbar when
|
||||
* scrolling up and to hide it when scrolling down.
|
||||
*/
|
||||
diffClamp,
|
||||
|
||||
/**
|
||||
* Starts an animation after the given delay.
|
||||
*/
|
||||
delay,
|
||||
/**
|
||||
* Starts an array of animations in order, waiting for each to complete
|
||||
* before starting the next. If the current running animation is stopped, no
|
||||
* following animations will be started.
|
||||
*/
|
||||
sequence,
|
||||
/**
|
||||
* Starts an array of animations all at the same time. By default, if one
|
||||
* of the animations is stopped, they will all be stopped. You can override
|
||||
* this with the `stopTogether` flag.
|
||||
*/
|
||||
parallel,
|
||||
/**
|
||||
* Array of animations may run in parallel (overlap), but are started in
|
||||
* sequence with successive delays. Nice for doing trailing effects.
|
||||
*/
|
||||
stagger,
|
||||
/**
|
||||
* Loops a given animation continuously, so that each time it reaches the
|
||||
* end, it resets and begins again from the start. Can specify number of
|
||||
* times to loop using the key 'iterations' in the config. Will loop without
|
||||
* blocking the UI thread if the child animation is set to 'useNativeDriver'.
|
||||
*/
|
||||
loop,
|
||||
|
||||
/**
|
||||
* Takes an array of mappings and extracts values from each arg accordingly,
|
||||
* then calls `setValue` on the mapped outputs. e.g.
|
||||
*
|
||||
*```javascript
|
||||
* onScroll={Animated.event(
|
||||
* [{nativeEvent: {contentOffset: {x: this._scrollX}}}],
|
||||
* {listener}, // Optional async listener
|
||||
* )}
|
||||
* ...
|
||||
* onPanResponderMove: Animated.event([
|
||||
* null, // raw event arg ignored
|
||||
* {dx: this._panX}, // gestureState arg
|
||||
* ]),
|
||||
*```
|
||||
*
|
||||
* Config is an object that may have the following options:
|
||||
*
|
||||
* - `listener`: Optional async listener.
|
||||
* - `useNativeDriver`: Uses the native driver when true. Default false.
|
||||
*/
|
||||
event,
|
||||
|
||||
/**
|
||||
* Make any React component Animatable. Used to create `Animated.View`, etc.
|
||||
*/
|
||||
createAnimatedComponent,
|
||||
|
||||
/**
|
||||
* Imperative API to attach an animated value to an event on a view. Prefer using
|
||||
* `Animated.event` with `useNativeDrive: true` if possible.
|
||||
*/
|
||||
attachNativeEvent,
|
||||
|
||||
/**
|
||||
* Advanced imperative API for snooping on animated events that are passed in through props. Use
|
||||
* values directly where possible.
|
||||
*/
|
||||
forkEvent,
|
||||
unforkEvent,
|
||||
|
||||
__PropsOnlyForTests: AnimatedProps,
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @flow
|
||||
* @providesModule AnimatedWeb
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var AnimatedImplementation = require('AnimatedImplementation');
|
||||
|
||||
module.exports = {
|
||||
...AnimatedImplementation,
|
||||
div: AnimatedImplementation.createAnimatedComponent('div'),
|
||||
span: AnimatedImplementation.createAnimatedComponent('span'),
|
||||
img: AnimatedImplementation.createAnimatedComponent('img'),
|
||||
};
|
||||
@@ -1,267 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule Easing
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
let ease;
|
||||
|
||||
/**
|
||||
* The `Easing` module implements common easing functions. This module is used
|
||||
* by [Animate.timing()](docs/animate.html#timing) to convey physically
|
||||
* believable motion in animations.
|
||||
*
|
||||
* You can find a visualization of some common easing functions at
|
||||
* http://easings.net/
|
||||
*
|
||||
* ### Predefined animations
|
||||
*
|
||||
* The `Easing` module provides several predefined animations through the
|
||||
* following methods:
|
||||
*
|
||||
* - [`back`](docs/easing.html#back) provides a simple animation where the
|
||||
* object goes slightly back before moving forward
|
||||
* - [`bounce`](docs/easing.html#bounce) provides a bouncing animation
|
||||
* - [`ease`](docs/easing.html#ease) provides a simple inertial animation
|
||||
* - [`elastic`](docs/easing.html#elastic) provides a simple spring interaction
|
||||
*
|
||||
* ### Standard functions
|
||||
*
|
||||
* Three standard easing functions are provided:
|
||||
*
|
||||
* - [`linear`](docs/easing.html#linear)
|
||||
* - [`quad`](docs/easing.html#quad)
|
||||
* - [`cubic`](docs/easing.html#cubic)
|
||||
*
|
||||
* The [`poly`](docs/easing.html#poly) function can be used to implement
|
||||
* quartic, quintic, and other higher power functions.
|
||||
*
|
||||
* ### Additional functions
|
||||
*
|
||||
* Additional mathematical functions are provided by the following methods:
|
||||
*
|
||||
* - [`bezier`](docs/easing.html#bezier) provides a cubic bezier curve
|
||||
* - [`circle`](docs/easing.html#circle) provides a circular function
|
||||
* - [`sin`](docs/easing.html#sin) provides a sinusoidal function
|
||||
* - [`exp`](docs/easing.html#exp) provides an exponential function
|
||||
*
|
||||
* The following helpers are used to modify other easing functions.
|
||||
*
|
||||
* - [`in`](docs/easing.html#in) runs an easing function forwards
|
||||
* - [`inOut`](docs/easing.html#inout) makes any easing function symmetrical
|
||||
* - [`out`](docs/easing.html#out) runs an easing function backwards
|
||||
*/
|
||||
class Easing {
|
||||
/**
|
||||
* A stepping function, returns 1 for any positive value of `n`.
|
||||
*/
|
||||
static step0(n) {
|
||||
return n > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* A stepping function, returns 1 if `n` is greater than or equal to 1.
|
||||
*/
|
||||
static step1(n) {
|
||||
return n >= 1 ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* A linear function, `f(t) = t`. Position correlates to elapsed time one to
|
||||
* one.
|
||||
*
|
||||
* http://cubic-bezier.com/#0,0,1,1
|
||||
*/
|
||||
static linear(t) {
|
||||
return t;
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple inertial interaction, similar to an object slowly accelerating to
|
||||
* speed.
|
||||
*
|
||||
* http://cubic-bezier.com/#.42,0,1,1
|
||||
*/
|
||||
static ease(t: number): number {
|
||||
if (!ease) {
|
||||
ease = Easing.bezier(0.42, 0, 1, 1);
|
||||
}
|
||||
return ease(t);
|
||||
}
|
||||
|
||||
/**
|
||||
* A quadratic function, `f(t) = t * t`. Position equals the square of elapsed
|
||||
* time.
|
||||
*
|
||||
* http://easings.net/#easeInQuad
|
||||
*/
|
||||
static quad(t) {
|
||||
return t * t;
|
||||
}
|
||||
|
||||
/**
|
||||
* A cubic function, `f(t) = t * t * t`. Position equals the cube of elapsed
|
||||
* time.
|
||||
*
|
||||
* http://easings.net/#easeInCubic
|
||||
*/
|
||||
static cubic(t) {
|
||||
return t * t * t;
|
||||
}
|
||||
|
||||
/**
|
||||
* A power function. Position is equal to the Nth power of elapsed time.
|
||||
*
|
||||
* n = 4: http://easings.net/#easeInQuart
|
||||
* n = 5: http://easings.net/#easeInQuint
|
||||
*/
|
||||
static poly(n) {
|
||||
return (t) => Math.pow(t, n);
|
||||
}
|
||||
|
||||
/**
|
||||
* A sinusoidal function.
|
||||
*
|
||||
* http://easings.net/#easeInSine
|
||||
*/
|
||||
static sin(t) {
|
||||
return 1 - Math.cos(t * Math.PI / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* A circular function.
|
||||
*
|
||||
* http://easings.net/#easeInCirc
|
||||
*/
|
||||
static circle(t) {
|
||||
return 1 - Math.sqrt(1 - t * t);
|
||||
}
|
||||
|
||||
/**
|
||||
* An exponential function.
|
||||
*
|
||||
* http://easings.net/#easeInExpo
|
||||
*/
|
||||
static exp(t) {
|
||||
return Math.pow(2, 10 * (t - 1));
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple elastic interaction, similar to a spring oscillating back and
|
||||
* forth.
|
||||
*
|
||||
* Default bounciness is 1, which overshoots a little bit once. 0 bounciness
|
||||
* doesn't overshoot at all, and bounciness of N > 1 will overshoot about N
|
||||
* times.
|
||||
*
|
||||
* http://easings.net/#easeInElastic
|
||||
*
|
||||
* Wolfram Plots:
|
||||
*
|
||||
* - http://tiny.cc/elastic_b_1 (bounciness = 1, default)
|
||||
* - http://tiny.cc/elastic_b_3 (bounciness = 3)
|
||||
*/
|
||||
static elastic(bounciness: number = 1): (t: number) => number {
|
||||
const p = bounciness * Math.PI;
|
||||
return (t) => 1 - Math.pow(Math.cos(t * Math.PI / 2), 3) * Math.cos(t * p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Use with `Animated.parallel()` to create a simple effect where the object
|
||||
* animates back slightly as the animation starts.
|
||||
*
|
||||
* Wolfram Plot:
|
||||
*
|
||||
* - http://tiny.cc/back_default (s = 1.70158, default)
|
||||
*/
|
||||
static back(s: number): (t: number) => number {
|
||||
if (s === undefined) {
|
||||
s = 1.70158;
|
||||
}
|
||||
return (t) => t * t * ((s + 1) * t - s);
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a simple bouncing effect.
|
||||
*
|
||||
* http://easings.net/#easeInBounce
|
||||
*/
|
||||
static bounce(t: number): number {
|
||||
if (t < 1 / 2.75) {
|
||||
return 7.5625 * t * t;
|
||||
}
|
||||
|
||||
if (t < 2 / 2.75) {
|
||||
t -= 1.5 / 2.75;
|
||||
return 7.5625 * t * t + 0.75;
|
||||
}
|
||||
|
||||
if (t < 2.5 / 2.75) {
|
||||
t -= 2.25 / 2.75;
|
||||
return 7.5625 * t * t + 0.9375;
|
||||
}
|
||||
|
||||
t -= 2.625 / 2.75;
|
||||
return 7.5625 * t * t + 0.984375;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a cubic bezier curve, equivalent to CSS Transitions'
|
||||
* `transition-timing-function`.
|
||||
*
|
||||
* A useful tool to visualize cubic bezier curves can be found at
|
||||
* http://cubic-bezier.com/
|
||||
*/
|
||||
static bezier(
|
||||
x1: number,
|
||||
y1: number,
|
||||
x2: number,
|
||||
y2: number
|
||||
): (t: number) => number {
|
||||
const _bezier = require('bezier');
|
||||
return _bezier(x1, y1, x2, y2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an easing function forwards.
|
||||
*/
|
||||
static in(
|
||||
easing: (t: number) => number,
|
||||
): (t: number) => number {
|
||||
return easing;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs an easing function backwards.
|
||||
*/
|
||||
static out(
|
||||
easing: (t: number) => number,
|
||||
): (t: number) => number {
|
||||
return (t) => 1 - easing(1 - t);
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes any easing function symmetrical. The easing function will run
|
||||
* forwards for half of the duration, then backwards for the rest of the
|
||||
* duration.
|
||||
*/
|
||||
static inOut(
|
||||
easing: (t: number) => number,
|
||||
): (t: number) => number {
|
||||
return (t) => {
|
||||
if (t < 0.5) {
|
||||
return easing(t * 2) / 2;
|
||||
}
|
||||
return 1 - easing((1 - t) * 2) / 2;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Easing;
|
||||
@@ -1,253 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule NativeAnimatedHelper
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const NativeAnimatedModule = require('NativeModules').NativeAnimatedModule;
|
||||
const NativeEventEmitter = require('NativeEventEmitter');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type {AnimationConfig} from './animations/Animation';
|
||||
import type {EventConfig} from './AnimatedEvent';
|
||||
|
||||
let __nativeAnimatedNodeTagCount = 1; /* used for animated nodes */
|
||||
let __nativeAnimationIdCount = 1; /* used for started animations */
|
||||
|
||||
type EndResult = {finished: boolean};
|
||||
type EndCallback = (result: EndResult) => void;
|
||||
type EventMapping = {
|
||||
nativeEventPath: Array<string>,
|
||||
animatedValueTag: ?number,
|
||||
};
|
||||
|
||||
let nativeEventEmitter;
|
||||
|
||||
/**
|
||||
* Simple wrappers around NativeAnimatedModule to provide flow and autocmplete support for
|
||||
* the native module methods
|
||||
*/
|
||||
const API = {
|
||||
createAnimatedNode: function(tag: ?number, config: Object): void {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.createAnimatedNode(tag, config);
|
||||
},
|
||||
startListeningToAnimatedNodeValue: function(tag: ?number) {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.startListeningToAnimatedNodeValue(tag);
|
||||
},
|
||||
stopListeningToAnimatedNodeValue: function(tag: ?number) {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.stopListeningToAnimatedNodeValue(tag);
|
||||
},
|
||||
connectAnimatedNodes: function(parentTag: ?number, childTag: ?number): void {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.connectAnimatedNodes(parentTag, childTag);
|
||||
},
|
||||
disconnectAnimatedNodes: function(
|
||||
parentTag: ?number,
|
||||
childTag: ?number,
|
||||
): void {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.disconnectAnimatedNodes(parentTag, childTag);
|
||||
},
|
||||
startAnimatingNode: function(
|
||||
animationId: ?number,
|
||||
nodeTag: ?number,
|
||||
config: Object,
|
||||
endCallback: EndCallback,
|
||||
): void {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.startAnimatingNode(
|
||||
animationId,
|
||||
nodeTag,
|
||||
config,
|
||||
endCallback,
|
||||
);
|
||||
},
|
||||
stopAnimation: function(animationId: ?number) {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.stopAnimation(animationId);
|
||||
},
|
||||
setAnimatedNodeValue: function(nodeTag: ?number, value: ?number): void {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.setAnimatedNodeValue(nodeTag, value);
|
||||
},
|
||||
setAnimatedNodeOffset: function(nodeTag: ?number, offset: ?number): void {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.setAnimatedNodeOffset(nodeTag, offset);
|
||||
},
|
||||
flattenAnimatedNodeOffset: function(nodeTag: ?number): void {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.flattenAnimatedNodeOffset(nodeTag);
|
||||
},
|
||||
extractAnimatedNodeOffset: function(nodeTag: ?number): void {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.extractAnimatedNodeOffset(nodeTag);
|
||||
},
|
||||
connectAnimatedNodeToView: function(
|
||||
nodeTag: ?number,
|
||||
viewTag: ?number,
|
||||
): void {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.connectAnimatedNodeToView(nodeTag, viewTag);
|
||||
},
|
||||
disconnectAnimatedNodeFromView: function(
|
||||
nodeTag: ?number,
|
||||
viewTag: ?number,
|
||||
): void {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.disconnectAnimatedNodeFromView(nodeTag, viewTag);
|
||||
},
|
||||
dropAnimatedNode: function(tag: ?number): void {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.dropAnimatedNode(tag);
|
||||
},
|
||||
addAnimatedEventToView: function(
|
||||
viewTag: ?number,
|
||||
eventName: string,
|
||||
eventMapping: EventMapping,
|
||||
) {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.addAnimatedEventToView(
|
||||
viewTag,
|
||||
eventName,
|
||||
eventMapping,
|
||||
);
|
||||
},
|
||||
removeAnimatedEventFromView(
|
||||
viewTag: ?number,
|
||||
eventName: string,
|
||||
animatedNodeTag: ?number,
|
||||
) {
|
||||
assertNativeAnimatedModule();
|
||||
NativeAnimatedModule.removeAnimatedEventFromView(
|
||||
viewTag,
|
||||
eventName,
|
||||
animatedNodeTag,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Styles allowed by the native animated implementation.
|
||||
*
|
||||
* In general native animated implementation should support any numeric property that doesn't need
|
||||
* to be updated through the shadow view hierarchy (all non-layout properties).
|
||||
*/
|
||||
const STYLES_WHITELIST = {
|
||||
opacity: true,
|
||||
transform: true,
|
||||
/* legacy android transform properties */
|
||||
scaleX: true,
|
||||
scaleY: true,
|
||||
translateX: true,
|
||||
translateY: true,
|
||||
};
|
||||
|
||||
const TRANSFORM_WHITELIST = {
|
||||
translateX: true,
|
||||
translateY: true,
|
||||
scale: true,
|
||||
scaleX: true,
|
||||
scaleY: true,
|
||||
rotate: true,
|
||||
rotateX: true,
|
||||
rotateY: true,
|
||||
perspective: true,
|
||||
};
|
||||
|
||||
function validateTransform(configs: Array<Object>): void {
|
||||
configs.forEach(config => {
|
||||
if (!TRANSFORM_WHITELIST.hasOwnProperty(config.property)) {
|
||||
throw new Error(
|
||||
`Property '${config.property}' is not supported by native animated module`,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function validateStyles(styles: Object): void {
|
||||
for (var key in styles) {
|
||||
if (!STYLES_WHITELIST.hasOwnProperty(key)) {
|
||||
throw new Error(
|
||||
`Style property '${key}' is not supported by native animated module`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function validateInterpolation(config: Object): void {
|
||||
var SUPPORTED_INTERPOLATION_PARAMS = {
|
||||
inputRange: true,
|
||||
outputRange: true,
|
||||
extrapolate: true,
|
||||
extrapolateRight: true,
|
||||
extrapolateLeft: true,
|
||||
};
|
||||
for (var key in config) {
|
||||
if (!SUPPORTED_INTERPOLATION_PARAMS.hasOwnProperty(key)) {
|
||||
throw new Error(
|
||||
`Interpolation property '${key}' is not supported by native animated module`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function generateNewNodeTag(): number {
|
||||
return __nativeAnimatedNodeTagCount++;
|
||||
}
|
||||
|
||||
function generateNewAnimationId(): number {
|
||||
return __nativeAnimationIdCount++;
|
||||
}
|
||||
|
||||
function assertNativeAnimatedModule(): void {
|
||||
invariant(NativeAnimatedModule, 'Native animated module is not available');
|
||||
}
|
||||
|
||||
let _warnedMissingNativeAnimated = false;
|
||||
|
||||
function shouldUseNativeDriver(config: AnimationConfig | EventConfig): boolean {
|
||||
if (config.useNativeDriver && !NativeAnimatedModule) {
|
||||
if (!_warnedMissingNativeAnimated) {
|
||||
console.warn(
|
||||
'Animated: `useNativeDriver` is not supported because the native ' +
|
||||
'animated module is missing. Falling back to JS-based animation. To ' +
|
||||
'resolve this, add `RCTAnimation` module to this app, or remove ' +
|
||||
'`useNativeDriver`. ' +
|
||||
'More info: https://github.com/facebook/react-native/issues/11094#issuecomment-263240420',
|
||||
);
|
||||
_warnedMissingNativeAnimated = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return config.useNativeDriver || false;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
API,
|
||||
validateStyles,
|
||||
validateTransform,
|
||||
validateInterpolation,
|
||||
generateNewNodeTag,
|
||||
generateNewAnimationId,
|
||||
assertNativeAnimatedModule,
|
||||
shouldUseNativeDriver,
|
||||
get nativeEventEmitter() {
|
||||
if (!nativeEventEmitter) {
|
||||
nativeEventEmitter = new NativeEventEmitter(NativeAnimatedModule);
|
||||
}
|
||||
return nativeEventEmitter;
|
||||
},
|
||||
};
|
||||
@@ -1,102 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule SpringConfig
|
||||
* @flow
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
type SpringConfigType = {
|
||||
tension: number,
|
||||
friction: number,
|
||||
};
|
||||
|
||||
function tensionFromOrigamiValue(oValue) {
|
||||
return (oValue - 30) * 3.62 + 194;
|
||||
}
|
||||
|
||||
function frictionFromOrigamiValue(oValue) {
|
||||
return (oValue - 8) * 3 + 25;
|
||||
}
|
||||
|
||||
function fromOrigamiTensionAndFriction(
|
||||
tension: number,
|
||||
friction: number,
|
||||
): SpringConfigType {
|
||||
return {
|
||||
tension: tensionFromOrigamiValue(tension),
|
||||
friction: frictionFromOrigamiValue(friction)
|
||||
};
|
||||
}
|
||||
|
||||
function fromBouncinessAndSpeed(
|
||||
bounciness: number,
|
||||
speed: number,
|
||||
): SpringConfigType {
|
||||
function normalize(value, startValue, endValue) {
|
||||
return (value - startValue) / (endValue - startValue);
|
||||
}
|
||||
|
||||
function projectNormal(n, start, end) {
|
||||
return start + (n * (end - start));
|
||||
}
|
||||
|
||||
function linearInterpolation(t, start, end) {
|
||||
return t * end + (1 - t) * start;
|
||||
}
|
||||
|
||||
function quadraticOutInterpolation(t, start, end) {
|
||||
return linearInterpolation(2 * t - t * t, start, end);
|
||||
}
|
||||
|
||||
function b3Friction1(x) {
|
||||
return (0.0007 * Math.pow(x, 3)) -
|
||||
(0.031 * Math.pow(x, 2)) + 0.64 * x + 1.28;
|
||||
}
|
||||
|
||||
function b3Friction2(x) {
|
||||
return (0.000044 * Math.pow(x, 3)) -
|
||||
(0.006 * Math.pow(x, 2)) + 0.36 * x + 2;
|
||||
}
|
||||
|
||||
function b3Friction3(x) {
|
||||
return (0.00000045 * Math.pow(x, 3)) -
|
||||
(0.000332 * Math.pow(x, 2)) + 0.1078 * x + 5.84;
|
||||
}
|
||||
|
||||
function b3Nobounce(tension) {
|
||||
if (tension <= 18) {
|
||||
return b3Friction1(tension);
|
||||
} else if (tension > 18 && tension <= 44) {
|
||||
return b3Friction2(tension);
|
||||
} else {
|
||||
return b3Friction3(tension);
|
||||
}
|
||||
}
|
||||
|
||||
var b = normalize(bounciness / 1.7, 0, 20);
|
||||
b = projectNormal(b, 0, 0.8);
|
||||
var s = normalize(speed / 1.7, 0, 20);
|
||||
var bouncyTension = projectNormal(s, 0.5, 200);
|
||||
var bouncyFriction = quadraticOutInterpolation(
|
||||
b,
|
||||
b3Nobounce(bouncyTension),
|
||||
0.01
|
||||
);
|
||||
|
||||
return {
|
||||
tension: tensionFromOrigamiValue(bouncyTension),
|
||||
friction: frictionFromOrigamiValue(bouncyFriction)
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
fromOrigamiTensionAndFriction,
|
||||
fromBouncinessAndSpeed,
|
||||
};
|
||||
@@ -1,780 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var Animated = require('Animated');
|
||||
describe('Animated tests', () => {
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
});
|
||||
|
||||
describe('Animated', () => {
|
||||
|
||||
it('works end to end', () => {
|
||||
var anim = new Animated.Value(0);
|
||||
|
||||
var callback = jest.fn();
|
||||
|
||||
var node = new Animated.__PropsOnlyForTests({
|
||||
style: {
|
||||
backgroundColor: 'red',
|
||||
opacity: anim,
|
||||
transform: [
|
||||
{translateX: anim.interpolate({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [100, 200],
|
||||
})},
|
||||
{scale: anim},
|
||||
],
|
||||
shadowOffset: {
|
||||
width: anim,
|
||||
height: anim,
|
||||
},
|
||||
}
|
||||
}, callback);
|
||||
|
||||
expect(anim.__getChildren().length).toBe(3);
|
||||
|
||||
expect(node.__getValue()).toEqual({
|
||||
style: {
|
||||
backgroundColor: 'red',
|
||||
opacity: 0,
|
||||
transform: [
|
||||
{translateX: 100},
|
||||
{scale: 0},
|
||||
],
|
||||
shadowOffset: {
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
anim.setValue(0.5);
|
||||
|
||||
expect(callback).toBeCalled();
|
||||
|
||||
expect(node.__getValue()).toEqual({
|
||||
style: {
|
||||
backgroundColor: 'red',
|
||||
opacity: 0.5,
|
||||
transform: [
|
||||
{translateX: 150},
|
||||
{scale: 0.5},
|
||||
],
|
||||
shadowOffset: {
|
||||
width: 0.5,
|
||||
height: 0.5,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
node.__detach();
|
||||
expect(anim.__getChildren().length).toBe(0);
|
||||
|
||||
anim.setValue(1);
|
||||
expect(callback.mock.calls.length).toBe(1);
|
||||
});
|
||||
|
||||
it('does not detach on updates', () => {
|
||||
var anim = new Animated.Value(0);
|
||||
anim.__detach = jest.fn();
|
||||
|
||||
var c = new Animated.View();
|
||||
c.props = {
|
||||
style: {
|
||||
opacity: anim,
|
||||
},
|
||||
};
|
||||
c.componentWillMount();
|
||||
|
||||
expect(anim.__detach).not.toBeCalled();
|
||||
c._component = {};
|
||||
c.componentWillReceiveProps({
|
||||
style: {
|
||||
opacity: anim,
|
||||
},
|
||||
});
|
||||
expect(anim.__detach).not.toBeCalled();
|
||||
|
||||
c.componentWillUnmount();
|
||||
expect(anim.__detach).toBeCalled();
|
||||
});
|
||||
|
||||
|
||||
it('stops animation when detached', () => {
|
||||
var anim = new Animated.Value(0);
|
||||
var callback = jest.fn();
|
||||
|
||||
var c = new Animated.View();
|
||||
c.props = {
|
||||
style: {
|
||||
opacity: anim,
|
||||
},
|
||||
};
|
||||
c.componentWillMount();
|
||||
|
||||
Animated.timing(anim, {toValue: 10, duration: 1000}).start(callback);
|
||||
c._component = {};
|
||||
c.componentWillUnmount();
|
||||
|
||||
expect(callback).toBeCalledWith({finished: false});
|
||||
expect(callback).toBeCalledWith({finished: false});
|
||||
});
|
||||
|
||||
it('triggers callback when spring is at rest', () => {
|
||||
var anim = new Animated.Value(0);
|
||||
var callback = jest.fn();
|
||||
Animated.spring(anim, {toValue: 0, velocity: 0}).start(callback);
|
||||
expect(callback).toBeCalled();
|
||||
});
|
||||
|
||||
it('send toValue when a spring stops', () => {
|
||||
var anim = new Animated.Value(0);
|
||||
var listener = jest.fn();
|
||||
anim.addListener(listener);
|
||||
Animated.spring(anim, {toValue: 15}).start();
|
||||
jest.runAllTimers();
|
||||
var lastValue = listener.mock.calls[listener.mock.calls.length - 2][0].value;
|
||||
expect(lastValue).not.toBe(15);
|
||||
expect(lastValue).toBeCloseTo(15);
|
||||
expect(anim.__getValue()).toBe(15);
|
||||
});
|
||||
|
||||
it('convert to JSON', () => {
|
||||
expect(JSON.stringify(new Animated.Value(10))).toBe('10');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('Animated Sequence', () => {
|
||||
|
||||
it('works with an empty sequence', () => {
|
||||
var cb = jest.fn();
|
||||
Animated.sequence([]).start(cb);
|
||||
expect(cb).toBeCalledWith({finished: true});
|
||||
});
|
||||
|
||||
it('sequences well', () => {
|
||||
var anim1 = {start: jest.fn()};
|
||||
var anim2 = {start: jest.fn()};
|
||||
var cb = jest.fn();
|
||||
|
||||
var seq = Animated.sequence([anim1, anim2]);
|
||||
|
||||
expect(anim1.start).not.toBeCalled();
|
||||
expect(anim2.start).not.toBeCalled();
|
||||
|
||||
seq.start(cb);
|
||||
|
||||
expect(anim1.start).toBeCalled();
|
||||
expect(anim2.start).not.toBeCalled();
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
anim1.start.mock.calls[0][0]({finished: true});
|
||||
|
||||
expect(anim2.start).toBeCalled();
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
anim2.start.mock.calls[0][0]({finished: true});
|
||||
expect(cb).toBeCalledWith({finished: true});
|
||||
});
|
||||
|
||||
it('supports interrupting sequence', () => {
|
||||
var anim1 = {start: jest.fn()};
|
||||
var anim2 = {start: jest.fn()};
|
||||
var cb = jest.fn();
|
||||
|
||||
Animated.sequence([anim1, anim2]).start(cb);
|
||||
|
||||
anim1.start.mock.calls[0][0]({finished: false});
|
||||
|
||||
expect(anim1.start).toBeCalled();
|
||||
expect(anim2.start).not.toBeCalled();
|
||||
expect(cb).toBeCalledWith({finished: false});
|
||||
});
|
||||
|
||||
it('supports stopping sequence', () => {
|
||||
var anim1 = {start: jest.fn(), stop: jest.fn()};
|
||||
var anim2 = {start: jest.fn(), stop: jest.fn()};
|
||||
var cb = jest.fn();
|
||||
|
||||
var seq = Animated.sequence([anim1, anim2]);
|
||||
seq.start(cb);
|
||||
seq.stop();
|
||||
|
||||
expect(anim1.stop).toBeCalled();
|
||||
expect(anim2.stop).not.toBeCalled();
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
anim1.start.mock.calls[0][0]({finished: false});
|
||||
|
||||
expect(cb).toBeCalledWith({finished: false});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Animated Loop', () => {
|
||||
|
||||
it('loops indefinitely if config not specified', () => {
|
||||
var animation = {start: jest.fn(), reset: jest.fn(), _isUsingNativeDriver: () => false};
|
||||
var cb = jest.fn();
|
||||
|
||||
var loop = Animated.loop(animation);
|
||||
|
||||
expect(animation.start).not.toBeCalled();
|
||||
|
||||
loop.start(cb);
|
||||
|
||||
expect(animation.start).toBeCalled();
|
||||
expect(animation.reset).toHaveBeenCalledTimes(1);
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
animation.start.mock.calls[0][0]({finished: true}); // End of loop 1
|
||||
expect(animation.reset).toHaveBeenCalledTimes(2);
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
animation.start.mock.calls[0][0]({finished: true}); // End of loop 2
|
||||
expect(animation.reset).toHaveBeenCalledTimes(3);
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
animation.start.mock.calls[0][0]({finished: true}); // End of loop 3
|
||||
expect(animation.reset).toHaveBeenCalledTimes(4);
|
||||
expect(cb).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('loops indefinitely if iterations is -1', () => {
|
||||
var animation = {start: jest.fn(), reset: jest.fn(), _isUsingNativeDriver: () => false};
|
||||
var cb = jest.fn();
|
||||
|
||||
var loop = Animated.loop(animation, { iterations: -1 });
|
||||
|
||||
expect(animation.start).not.toBeCalled();
|
||||
|
||||
loop.start(cb);
|
||||
|
||||
expect(animation.start).toBeCalled();
|
||||
expect(animation.reset).toHaveBeenCalledTimes(1);
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
animation.start.mock.calls[0][0]({finished: true}); // End of loop 1
|
||||
expect(animation.reset).toHaveBeenCalledTimes(2);
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
animation.start.mock.calls[0][0]({finished: true}); // End of loop 2
|
||||
expect(animation.reset).toHaveBeenCalledTimes(3);
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
animation.start.mock.calls[0][0]({finished: true}); // End of loop 3
|
||||
expect(animation.reset).toHaveBeenCalledTimes(4);
|
||||
expect(cb).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('loops indefinitely if iterations not specified', () => {
|
||||
var animation = {start: jest.fn(), reset: jest.fn(), _isUsingNativeDriver: () => false};
|
||||
var cb = jest.fn();
|
||||
|
||||
var loop = Animated.loop(animation, { anotherKey: 'value' });
|
||||
|
||||
expect(animation.start).not.toBeCalled();
|
||||
|
||||
loop.start(cb);
|
||||
|
||||
expect(animation.start).toBeCalled();
|
||||
expect(animation.reset).toHaveBeenCalledTimes(1);
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
animation.start.mock.calls[0][0]({finished: true}); // End of loop 1
|
||||
expect(animation.reset).toHaveBeenCalledTimes(2);
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
animation.start.mock.calls[0][0]({finished: true}); // End of loop 2
|
||||
expect(animation.reset).toHaveBeenCalledTimes(3);
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
animation.start.mock.calls[0][0]({finished: true}); // End of loop 3
|
||||
expect(animation.reset).toHaveBeenCalledTimes(4);
|
||||
expect(cb).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('loops three times if iterations is 3', () => {
|
||||
var animation = {start: jest.fn(), reset: jest.fn(), _isUsingNativeDriver: () => false};
|
||||
var cb = jest.fn();
|
||||
|
||||
var loop = Animated.loop(animation, { iterations: 3 });
|
||||
|
||||
expect(animation.start).not.toBeCalled();
|
||||
|
||||
loop.start(cb);
|
||||
|
||||
expect(animation.start).toBeCalled();
|
||||
expect(animation.reset).toHaveBeenCalledTimes(1);
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
animation.start.mock.calls[0][0]({finished: true}); // End of loop 1
|
||||
expect(animation.reset).toHaveBeenCalledTimes(2);
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
animation.start.mock.calls[0][0]({finished: true}); // End of loop 2
|
||||
expect(animation.reset).toHaveBeenCalledTimes(3);
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
animation.start.mock.calls[0][0]({finished: true}); // End of loop 3
|
||||
expect(animation.reset).toHaveBeenCalledTimes(3);
|
||||
expect(cb).toBeCalledWith({finished: true});
|
||||
});
|
||||
|
||||
it('does not loop if iterations is 1', () => {
|
||||
var animation = {start: jest.fn(), reset: jest.fn(), _isUsingNativeDriver: () => false};
|
||||
var cb = jest.fn();
|
||||
|
||||
var loop = Animated.loop(animation, { iterations: 1 });
|
||||
|
||||
expect(animation.start).not.toBeCalled();
|
||||
|
||||
loop.start(cb);
|
||||
|
||||
expect(animation.start).toBeCalled();
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
animation.start.mock.calls[0][0]({finished: true}); // End of loop 1
|
||||
expect(cb).toBeCalledWith({finished: true});
|
||||
});
|
||||
|
||||
it('does not animate if iterations is 0', () => {
|
||||
var animation = {start: jest.fn(), reset: jest.fn(), _isUsingNativeDriver: () => false};
|
||||
var cb = jest.fn();
|
||||
|
||||
var loop = Animated.loop(animation, { iterations: 0 });
|
||||
|
||||
expect(animation.start).not.toBeCalled();
|
||||
|
||||
loop.start(cb);
|
||||
|
||||
expect(animation.start).not.toBeCalled();
|
||||
expect(cb).toBeCalledWith({ finished: true });
|
||||
});
|
||||
|
||||
it('supports interrupting an indefinite loop', () => {
|
||||
var animation = {start: jest.fn(), reset: jest.fn(), _isUsingNativeDriver: () => false};
|
||||
var cb = jest.fn();
|
||||
|
||||
Animated.loop(animation).start(cb);
|
||||
expect(animation.start).toBeCalled();
|
||||
expect(animation.reset).toHaveBeenCalledTimes(1);
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
animation.start.mock.calls[0][0]({finished: true}); // End of loop 1
|
||||
expect(animation.reset).toHaveBeenCalledTimes(2);
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
animation.start.mock.calls[0][0]({finished: false}); // Interrupt loop
|
||||
expect(animation.reset).toHaveBeenCalledTimes(2);
|
||||
expect(cb).toBeCalledWith({finished: false});
|
||||
});
|
||||
|
||||
it('supports stopping loop', () => {
|
||||
var animation = {start: jest.fn(), stop: jest.fn(), reset: jest.fn(), _isUsingNativeDriver: () => false};
|
||||
var cb = jest.fn();
|
||||
|
||||
var loop = Animated.loop(animation);
|
||||
loop.start(cb);
|
||||
loop.stop();
|
||||
|
||||
expect(animation.start).toBeCalled();
|
||||
expect(animation.reset).toHaveBeenCalledTimes(1);
|
||||
expect(animation.stop).toBeCalled();
|
||||
|
||||
animation.start.mock.calls[0][0]({finished: false}); // Interrupt loop
|
||||
expect(animation.reset).toHaveBeenCalledTimes(1);
|
||||
expect(cb).toBeCalledWith({finished: false});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Animated Parallel', () => {
|
||||
|
||||
it('works with an empty parallel', () => {
|
||||
var cb = jest.fn();
|
||||
Animated.parallel([]).start(cb);
|
||||
expect(cb).toBeCalledWith({finished: true});
|
||||
});
|
||||
|
||||
it('works with an empty element in array', () => {
|
||||
var anim1 = {start: jest.fn()};
|
||||
var cb = jest.fn();
|
||||
Animated.parallel([null, anim1]).start(cb);
|
||||
|
||||
expect(anim1.start).toBeCalled();
|
||||
anim1.start.mock.calls[0][0]({finished: true});
|
||||
|
||||
expect(cb).toBeCalledWith({finished: true});
|
||||
});
|
||||
|
||||
it('parellelizes well', () => {
|
||||
var anim1 = {start: jest.fn()};
|
||||
var anim2 = {start: jest.fn()};
|
||||
var cb = jest.fn();
|
||||
|
||||
var par = Animated.parallel([anim1, anim2]);
|
||||
|
||||
expect(anim1.start).not.toBeCalled();
|
||||
expect(anim2.start).not.toBeCalled();
|
||||
|
||||
par.start(cb);
|
||||
|
||||
expect(anim1.start).toBeCalled();
|
||||
expect(anim2.start).toBeCalled();
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
anim1.start.mock.calls[0][0]({finished: true});
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
anim2.start.mock.calls[0][0]({finished: true});
|
||||
expect(cb).toBeCalledWith({finished: true});
|
||||
});
|
||||
|
||||
it('supports stopping parallel', () => {
|
||||
var anim1 = {start: jest.fn(), stop: jest.fn()};
|
||||
var anim2 = {start: jest.fn(), stop: jest.fn()};
|
||||
var cb = jest.fn();
|
||||
|
||||
var seq = Animated.parallel([anim1, anim2]);
|
||||
seq.start(cb);
|
||||
seq.stop();
|
||||
|
||||
expect(anim1.stop).toBeCalled();
|
||||
expect(anim2.stop).toBeCalled();
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
anim1.start.mock.calls[0][0]({finished: false});
|
||||
expect(cb).not.toBeCalled();
|
||||
|
||||
anim2.start.mock.calls[0][0]({finished: false});
|
||||
expect(cb).toBeCalledWith({finished: false});
|
||||
});
|
||||
|
||||
|
||||
it('does not call stop more than once when stopping', () => {
|
||||
var anim1 = {start: jest.fn(), stop: jest.fn()};
|
||||
var anim2 = {start: jest.fn(), stop: jest.fn()};
|
||||
var anim3 = {start: jest.fn(), stop: jest.fn()};
|
||||
var cb = jest.fn();
|
||||
|
||||
var seq = Animated.parallel([anim1, anim2, anim3]);
|
||||
seq.start(cb);
|
||||
|
||||
anim1.start.mock.calls[0][0]({finished: false});
|
||||
|
||||
expect(anim1.stop.mock.calls.length).toBe(0);
|
||||
expect(anim2.stop.mock.calls.length).toBe(1);
|
||||
expect(anim3.stop.mock.calls.length).toBe(1);
|
||||
|
||||
anim2.start.mock.calls[0][0]({finished: false});
|
||||
|
||||
expect(anim1.stop.mock.calls.length).toBe(0);
|
||||
expect(anim2.stop.mock.calls.length).toBe(1);
|
||||
expect(anim3.stop.mock.calls.length).toBe(1);
|
||||
|
||||
anim3.start.mock.calls[0][0]({finished: false});
|
||||
|
||||
expect(anim1.stop.mock.calls.length).toBe(0);
|
||||
expect(anim2.stop.mock.calls.length).toBe(1);
|
||||
expect(anim3.stop.mock.calls.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Animated delays', () => {
|
||||
it('should call anim after delay in sequence', () => {
|
||||
var anim = {start: jest.fn(), stop: jest.fn()};
|
||||
var cb = jest.fn();
|
||||
Animated.sequence([
|
||||
Animated.delay(1000),
|
||||
anim,
|
||||
]).start(cb);
|
||||
jest.runAllTimers();
|
||||
expect(anim.start.mock.calls.length).toBe(1);
|
||||
expect(cb).not.toBeCalled();
|
||||
anim.start.mock.calls[0][0]({finished: true});
|
||||
expect(cb).toBeCalledWith({finished: true});
|
||||
});
|
||||
it('should run stagger to end', () => {
|
||||
var cb = jest.fn();
|
||||
Animated.stagger(1000, [
|
||||
Animated.delay(1000),
|
||||
Animated.delay(1000),
|
||||
Animated.delay(1000),
|
||||
]).start(cb);
|
||||
jest.runAllTimers();
|
||||
expect(cb).toBeCalledWith({finished: true});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Animated Events', () => {
|
||||
it('should map events', () => {
|
||||
var value = new Animated.Value(0);
|
||||
var handler = Animated.event(
|
||||
[null, {state: {foo: value}}],
|
||||
);
|
||||
handler({bar: 'ignoreBar'}, {state: {baz: 'ignoreBaz', foo: 42}});
|
||||
expect(value.__getValue()).toBe(42);
|
||||
});
|
||||
it('should call listeners', () => {
|
||||
var value = new Animated.Value(0);
|
||||
var listener = jest.fn();
|
||||
var handler = Animated.event(
|
||||
[{foo: value}],
|
||||
{listener},
|
||||
);
|
||||
handler({foo: 42});
|
||||
expect(value.__getValue()).toBe(42);
|
||||
expect(listener.mock.calls.length).toBe(1);
|
||||
expect(listener).toBeCalledWith({foo: 42});
|
||||
});
|
||||
it('should call forked event listeners', () => {
|
||||
var value = new Animated.Value(0);
|
||||
var listener = jest.fn();
|
||||
var handler = Animated.event(
|
||||
[{foo: value}],
|
||||
{listener},
|
||||
);
|
||||
var listener2 = jest.fn();
|
||||
var forkedHandler = Animated.forkEvent(handler, listener2);
|
||||
forkedHandler({foo: 42});
|
||||
expect(value.__getValue()).toBe(42);
|
||||
expect(listener.mock.calls.length).toBe(1);
|
||||
expect(listener).toBeCalledWith({foo: 42});
|
||||
expect(listener2.mock.calls.length).toBe(1);
|
||||
expect(listener2).toBeCalledWith({foo: 42});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Animated Interactions', () => {
|
||||
/*eslint-disable no-shadow*/
|
||||
var Animated;
|
||||
/*eslint-enable*/
|
||||
var InteractionManager;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.mock('InteractionManager');
|
||||
Animated = require('Animated');
|
||||
InteractionManager = require('InteractionManager');
|
||||
});
|
||||
|
||||
afterEach(()=> {
|
||||
jest.unmock('InteractionManager');
|
||||
});
|
||||
|
||||
it('registers an interaction by default', () => {
|
||||
InteractionManager.createInteractionHandle.mockReturnValue(777);
|
||||
|
||||
var value = new Animated.Value(0);
|
||||
var callback = jest.fn();
|
||||
Animated.timing(value, {
|
||||
toValue: 100,
|
||||
duration: 100,
|
||||
}).start(callback);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(InteractionManager.createInteractionHandle).toBeCalled();
|
||||
expect(InteractionManager.clearInteractionHandle).toBeCalledWith(777);
|
||||
expect(callback).toBeCalledWith({finished: true});
|
||||
});
|
||||
|
||||
it('does not register an interaction when specified', () => {
|
||||
var value = new Animated.Value(0);
|
||||
var callback = jest.fn();
|
||||
Animated.timing(value, {
|
||||
toValue: 100,
|
||||
duration: 100,
|
||||
isInteraction: false,
|
||||
}).start(callback);
|
||||
jest.runAllTimers();
|
||||
|
||||
expect(InteractionManager.createInteractionHandle).not.toBeCalled();
|
||||
expect(InteractionManager.clearInteractionHandle).not.toBeCalled();
|
||||
expect(callback).toBeCalledWith({finished: true});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Animated Tracking', () => {
|
||||
it('should track values', () => {
|
||||
var value1 = new Animated.Value(0);
|
||||
var value2 = new Animated.Value(0);
|
||||
Animated.timing(value2, {
|
||||
toValue: value1,
|
||||
duration: 0,
|
||||
}).start();
|
||||
value1.setValue(42);
|
||||
expect(value2.__getValue()).toBe(42);
|
||||
value1.setValue(7);
|
||||
expect(value2.__getValue()).toBe(7);
|
||||
});
|
||||
|
||||
it('should track interpolated values', () => {
|
||||
var value1 = new Animated.Value(0);
|
||||
var value2 = new Animated.Value(0);
|
||||
Animated.timing(value2, {
|
||||
toValue: value1.interpolate({
|
||||
inputRange: [0, 2],
|
||||
outputRange: [0, 1]
|
||||
}),
|
||||
duration: 0,
|
||||
}).start();
|
||||
value1.setValue(42);
|
||||
expect(value2.__getValue()).toBe(42 / 2);
|
||||
});
|
||||
|
||||
it('should stop tracking when animated', () => {
|
||||
var value1 = new Animated.Value(0);
|
||||
var value2 = new Animated.Value(0);
|
||||
Animated.timing(value2, {
|
||||
toValue: value1,
|
||||
duration: 0,
|
||||
}).start();
|
||||
value1.setValue(42);
|
||||
expect(value2.__getValue()).toBe(42);
|
||||
Animated.timing(value2, {
|
||||
toValue: 7,
|
||||
duration: 0,
|
||||
}).start();
|
||||
value1.setValue(1492);
|
||||
expect(value2.__getValue()).toBe(7);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Animated Vectors', () => {
|
||||
it('should animate vectors', () => {
|
||||
var vec = new Animated.ValueXY();
|
||||
|
||||
var callback = jest.fn();
|
||||
|
||||
var node = new Animated.__PropsOnlyForTests({
|
||||
style: {
|
||||
opacity: vec.x.interpolate({
|
||||
inputRange: [0, 42],
|
||||
outputRange: [0.2, 0.8],
|
||||
}),
|
||||
transform: vec.getTranslateTransform(),
|
||||
...vec.getLayout(),
|
||||
}
|
||||
}, callback);
|
||||
|
||||
expect(node.__getValue()).toEqual({
|
||||
style: {
|
||||
opacity: 0.2,
|
||||
transform: [
|
||||
{translateX: 0},
|
||||
{translateY: 0},
|
||||
],
|
||||
left: 0,
|
||||
top: 0,
|
||||
},
|
||||
});
|
||||
|
||||
vec.setValue({x: 42, y: 1492});
|
||||
|
||||
expect(callback.mock.calls.length).toBe(2); // once each for x, y
|
||||
|
||||
expect(node.__getValue()).toEqual({
|
||||
style: {
|
||||
opacity: 0.8,
|
||||
transform: [
|
||||
{translateX: 42},
|
||||
{translateY: 1492},
|
||||
],
|
||||
left: 42,
|
||||
top: 1492,
|
||||
},
|
||||
});
|
||||
|
||||
node.__detach();
|
||||
|
||||
vec.setValue({x: 1, y: 1});
|
||||
expect(callback.mock.calls.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should track vectors', () => {
|
||||
var value1 = new Animated.ValueXY();
|
||||
var value2 = new Animated.ValueXY();
|
||||
Animated.timing(value2, {
|
||||
toValue: value1,
|
||||
duration: 0,
|
||||
}).start();
|
||||
value1.setValue({x: 42, y: 1492});
|
||||
expect(value2.__getValue()).toEqual({x: 42, y: 1492});
|
||||
|
||||
// Make sure tracking keeps working (see stopTogether in ParallelConfig used
|
||||
// by maybeVectorAnim).
|
||||
value1.setValue({x: 3, y: 4});
|
||||
expect(value2.__getValue()).toEqual({x: 3, y: 4});
|
||||
});
|
||||
|
||||
it('should track with springs', () => {
|
||||
var value1 = new Animated.ValueXY();
|
||||
var value2 = new Animated.ValueXY();
|
||||
Animated.spring(value2, {
|
||||
toValue: value1,
|
||||
tension: 3000, // faster spring for faster test
|
||||
friction: 60,
|
||||
}).start();
|
||||
value1.setValue({x: 1, y: 1});
|
||||
jest.runAllTimers();
|
||||
expect(Math.round(value2.__getValue().x)).toEqual(1);
|
||||
expect(Math.round(value2.__getValue().y)).toEqual(1);
|
||||
value1.setValue({x: 2, y: 2});
|
||||
jest.runAllTimers();
|
||||
expect(Math.round(value2.__getValue().x)).toEqual(2);
|
||||
expect(Math.round(value2.__getValue().y)).toEqual(2);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Animated Listeners', () => {
|
||||
it('should get updates', () => {
|
||||
var value1 = new Animated.Value(0);
|
||||
var listener = jest.fn();
|
||||
var id = value1.addListener(listener);
|
||||
value1.setValue(42);
|
||||
expect(listener.mock.calls.length).toBe(1);
|
||||
expect(listener).toBeCalledWith({value: 42});
|
||||
expect(value1.__getValue()).toBe(42);
|
||||
value1.setValue(7);
|
||||
expect(listener.mock.calls.length).toBe(2);
|
||||
expect(listener).toBeCalledWith({value: 7});
|
||||
expect(value1.__getValue()).toBe(7);
|
||||
value1.removeListener(id);
|
||||
value1.setValue(1492);
|
||||
expect(listener.mock.calls.length).toBe(2);
|
||||
expect(value1.__getValue()).toBe(1492);
|
||||
});
|
||||
|
||||
it('should removeAll', () => {
|
||||
var value1 = new Animated.Value(0);
|
||||
var listener = jest.fn();
|
||||
[1,2,3,4].forEach(() => value1.addListener(listener));
|
||||
value1.setValue(42);
|
||||
expect(listener.mock.calls.length).toBe(4);
|
||||
expect(listener).toBeCalledWith({value: 42});
|
||||
value1.removeAllListeners();
|
||||
value1.setValue(7);
|
||||
expect(listener.mock.calls.length).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Animated Diff Clamp', () => {
|
||||
it('should get the proper value', () => {
|
||||
const inputValues = [0, 20, 40, 30, 0, -40, -10, -20, 0];
|
||||
const expectedValues = [0, 20, 20, 10, 0, 0, 20, 10, 20];
|
||||
const value = new Animated.Value(0);
|
||||
const diffClampValue = Animated.diffClamp(value, 0, 20);
|
||||
for (let i = 0; i < inputValues.length; i++) {
|
||||
value.setValue(inputValues[i]);
|
||||
expect(diffClampValue.__getValue()).toBe(expectedValues[i]);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,672 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
jest
|
||||
.setMock('Text', {})
|
||||
.setMock('View', {})
|
||||
.setMock('Image', {})
|
||||
.setMock('React', {Component: class {}})
|
||||
.setMock('NativeModules', {
|
||||
NativeAnimatedModule: {},
|
||||
})
|
||||
.mock('NativeEventEmitter')
|
||||
// findNodeHandle is imported from ReactNative so mock that whole module.
|
||||
.setMock('ReactNative', {findNodeHandle: () => 1});
|
||||
|
||||
const Animated = require('Animated');
|
||||
const NativeAnimatedHelper = require('NativeAnimatedHelper');
|
||||
|
||||
function createAndMountComponent(ComponentClass, props) {
|
||||
const component = new ComponentClass();
|
||||
component.props = props;
|
||||
component.componentWillMount();
|
||||
// Simulate that refs were set.
|
||||
component._component = {};
|
||||
component.componentDidMount();
|
||||
return component;
|
||||
}
|
||||
|
||||
describe('Native Animated', () => {
|
||||
|
||||
const nativeAnimatedModule = require('NativeModules').NativeAnimatedModule;
|
||||
|
||||
beforeEach(() => {
|
||||
nativeAnimatedModule.addAnimatedEventToView = jest.fn();
|
||||
nativeAnimatedModule.connectAnimatedNodes = jest.fn();
|
||||
nativeAnimatedModule.connectAnimatedNodeToView = jest.fn();
|
||||
nativeAnimatedModule.createAnimatedNode = jest.fn();
|
||||
nativeAnimatedModule.disconnectAnimatedNodeFromView = jest.fn();
|
||||
nativeAnimatedModule.disconnectAnimatedNodes = jest.fn();
|
||||
nativeAnimatedModule.dropAnimatedNode = jest.fn();
|
||||
nativeAnimatedModule.extractAnimatedNodeOffset = jest.fn();
|
||||
nativeAnimatedModule.flattenAnimatedNodeOffset = jest.fn();
|
||||
nativeAnimatedModule.removeAnimatedEventFromView = jest.fn();
|
||||
nativeAnimatedModule.setAnimatedNodeOffset = jest.fn();
|
||||
nativeAnimatedModule.setAnimatedNodeValue = jest.fn();
|
||||
nativeAnimatedModule.startAnimatingNode = jest.fn();
|
||||
nativeAnimatedModule.startListeningToAnimatedNodeValue = jest.fn();
|
||||
nativeAnimatedModule.stopAnimation = jest.fn();
|
||||
nativeAnimatedModule.stopListeningToAnimatedNodeValue = jest.fn();
|
||||
});
|
||||
|
||||
describe('Animated Value', () => {
|
||||
it('proxies `setValue` correctly', () => {
|
||||
const anim = new Animated.Value(0);
|
||||
Animated.timing(anim, {toValue: 10, duration: 1000, useNativeDriver: true}).start();
|
||||
|
||||
const c = createAndMountComponent(Animated.View, {
|
||||
style: {
|
||||
opacity: anim,
|
||||
},
|
||||
});
|
||||
|
||||
// We expect `setValue` not to propagate down to `setNativeProps`, otherwise it may try to access `setNativeProps`
|
||||
// via component refs table that we override here.
|
||||
c.refs = {
|
||||
node: {
|
||||
setNativeProps: jest.genMockFunction(),
|
||||
},
|
||||
};
|
||||
|
||||
anim.setValue(0.5);
|
||||
|
||||
expect(nativeAnimatedModule.setAnimatedNodeValue).toBeCalledWith(jasmine.any(Number), 0.5);
|
||||
expect(c.refs.node.setNativeProps).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set offset', () => {
|
||||
const anim = new Animated.Value(0);
|
||||
anim.setOffset(10);
|
||||
anim.__makeNative();
|
||||
createAndMountComponent(Animated.View, {
|
||||
style: {
|
||||
opacity: anim,
|
||||
},
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
||||
jasmine.any(Number),
|
||||
{type: 'value', value: 0, offset: 10},
|
||||
);
|
||||
anim.setOffset(20);
|
||||
expect(nativeAnimatedModule.setAnimatedNodeOffset)
|
||||
.toBeCalledWith(jasmine.any(Number), 20);
|
||||
});
|
||||
|
||||
it('should flatten offset', () => {
|
||||
const anim = new Animated.Value(0);
|
||||
anim.__makeNative();
|
||||
createAndMountComponent(Animated.View, {
|
||||
style: {
|
||||
opacity: anim,
|
||||
},
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
||||
jasmine.any(Number),
|
||||
{type: 'value', value: 0, offset: 0},
|
||||
);
|
||||
anim.flattenOffset();
|
||||
expect(nativeAnimatedModule.flattenAnimatedNodeOffset)
|
||||
.toBeCalledWith(jasmine.any(Number));
|
||||
});
|
||||
|
||||
it('should extract offset', () => {
|
||||
const anim = new Animated.Value(0);
|
||||
anim.__makeNative();
|
||||
createAndMountComponent(Animated.View, {
|
||||
style: {
|
||||
opacity: anim,
|
||||
},
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
||||
jasmine.any(Number),
|
||||
{type: 'value', value: 0, offset: 0},
|
||||
);
|
||||
anim.extractOffset();
|
||||
expect(nativeAnimatedModule.extractAnimatedNodeOffset)
|
||||
.toBeCalledWith(jasmine.any(Number));
|
||||
});
|
||||
});
|
||||
|
||||
describe('Animated Listeners', () => {
|
||||
it('should get updates', () => {
|
||||
const value1 = new Animated.Value(0);
|
||||
value1.__makeNative();
|
||||
const listener = jest.fn();
|
||||
const id = value1.addListener(listener);
|
||||
expect(nativeAnimatedModule.startListeningToAnimatedNodeValue)
|
||||
.toHaveBeenCalledWith(value1.__getNativeTag());
|
||||
|
||||
NativeAnimatedHelper.nativeEventEmitter.emit(
|
||||
'onAnimatedValueUpdate',
|
||||
{value: 42, tag: value1.__getNativeTag()},
|
||||
);
|
||||
expect(listener).toHaveBeenCalledTimes(1);
|
||||
expect(listener).toBeCalledWith({value: 42});
|
||||
expect(value1.__getValue()).toBe(42);
|
||||
|
||||
NativeAnimatedHelper.nativeEventEmitter.emit(
|
||||
'onAnimatedValueUpdate',
|
||||
{value: 7, tag: value1.__getNativeTag()},
|
||||
);
|
||||
expect(listener).toHaveBeenCalledTimes(2);
|
||||
expect(listener).toBeCalledWith({value: 7});
|
||||
expect(value1.__getValue()).toBe(7);
|
||||
|
||||
value1.removeListener(id);
|
||||
expect(nativeAnimatedModule.stopListeningToAnimatedNodeValue)
|
||||
.toHaveBeenCalledWith(value1.__getNativeTag());
|
||||
|
||||
NativeAnimatedHelper.nativeEventEmitter.emit(
|
||||
'onAnimatedValueUpdate',
|
||||
{value: 1492, tag: value1.__getNativeTag()},
|
||||
);
|
||||
expect(listener).toHaveBeenCalledTimes(2);
|
||||
expect(value1.__getValue()).toBe(7);
|
||||
});
|
||||
|
||||
it('should removeAll', () => {
|
||||
const value1 = new Animated.Value(0);
|
||||
value1.__makeNative();
|
||||
const listener = jest.fn();
|
||||
[1,2,3,4].forEach(() => value1.addListener(listener));
|
||||
expect(nativeAnimatedModule.startListeningToAnimatedNodeValue)
|
||||
.toHaveBeenCalledWith(value1.__getNativeTag());
|
||||
|
||||
NativeAnimatedHelper.nativeEventEmitter.emit(
|
||||
'onAnimatedValueUpdate',
|
||||
{value: 42, tag: value1.__getNativeTag()},
|
||||
);
|
||||
expect(listener).toHaveBeenCalledTimes(4);
|
||||
expect(listener).toBeCalledWith({value: 42});
|
||||
|
||||
value1.removeAllListeners();
|
||||
expect(nativeAnimatedModule.stopListeningToAnimatedNodeValue)
|
||||
.toHaveBeenCalledWith(value1.__getNativeTag());
|
||||
|
||||
NativeAnimatedHelper.nativeEventEmitter.emit(
|
||||
'onAnimatedValueUpdate',
|
||||
{value: 7, tag: value1.__getNativeTag()},
|
||||
);
|
||||
expect(listener).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Animated Events', () => {
|
||||
it('should map events', () => {
|
||||
const value = new Animated.Value(0);
|
||||
value.__makeNative();
|
||||
const event = Animated.event(
|
||||
[{nativeEvent: {state: {foo: value}}}],
|
||||
{useNativeDriver: true},
|
||||
);
|
||||
const c = createAndMountComponent(Animated.View, {onTouchMove: event});
|
||||
expect(nativeAnimatedModule.addAnimatedEventToView).toBeCalledWith(
|
||||
jasmine.any(Number),
|
||||
'onTouchMove',
|
||||
{nativeEventPath: ['state', 'foo'], animatedValueTag: value.__getNativeTag()},
|
||||
);
|
||||
|
||||
c.componentWillUnmount();
|
||||
expect(nativeAnimatedModule.removeAnimatedEventFromView).toBeCalledWith(
|
||||
jasmine.any(Number),
|
||||
'onTouchMove',
|
||||
value.__getNativeTag(),
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw on invalid event path', () => {
|
||||
const value = new Animated.Value(0);
|
||||
value.__makeNative();
|
||||
const event = Animated.event(
|
||||
[{notNativeEvent: {foo: value}}],
|
||||
{useNativeDriver: true},
|
||||
);
|
||||
expect(() => createAndMountComponent(Animated.View, {onTouchMove: event}))
|
||||
.toThrowError(/nativeEvent/);
|
||||
expect(nativeAnimatedModule.addAnimatedEventToView).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('should call listeners', () => {
|
||||
const value = new Animated.Value(0);
|
||||
value.__makeNative();
|
||||
const listener = jest.fn();
|
||||
const event = Animated.event(
|
||||
[{nativeEvent: {foo: value}}],
|
||||
{useNativeDriver: true, listener},
|
||||
);
|
||||
const handler = event.__getHandler();
|
||||
handler({foo: 42});
|
||||
expect(listener).toHaveBeenCalledTimes(1);
|
||||
expect(listener).toBeCalledWith({foo: 42});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Animated Graph', () => {
|
||||
it('creates and detaches nodes', () => {
|
||||
const anim = new Animated.Value(0);
|
||||
const c = createAndMountComponent(Animated.View, {
|
||||
style: {
|
||||
opacity: anim,
|
||||
},
|
||||
});
|
||||
|
||||
Animated.timing(anim, {toValue: 10, duration: 1000, useNativeDriver: true}).start();
|
||||
|
||||
c.componentWillUnmount();
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toHaveBeenCalledTimes(3);
|
||||
expect(nativeAnimatedModule.connectAnimatedNodes).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
||||
jasmine.any(Number),
|
||||
jasmine.any(Number),
|
||||
{type: 'frames', frames: jasmine.any(Array), toValue: jasmine.any(Number), iterations: 1},
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
expect(nativeAnimatedModule.disconnectAnimatedNodes).toHaveBeenCalledTimes(2);
|
||||
expect(nativeAnimatedModule.dropAnimatedNode).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it('sends a valid description for value, style and props nodes', () => {
|
||||
const anim = new Animated.Value(0);
|
||||
createAndMountComponent(Animated.View, {
|
||||
style: {
|
||||
opacity: anim,
|
||||
},
|
||||
});
|
||||
|
||||
Animated.timing(anim, {toValue: 10, duration: 1000, useNativeDriver: true}).start();
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(jasmine.any(Number), {type: 'value', value: 0, offset: 0});
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(jasmine.any(Number), {type: 'style', style: {opacity: jasmine.any(Number)}});
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(jasmine.any(Number), {type: 'props', props: {style: jasmine.any(Number)}});
|
||||
});
|
||||
|
||||
it('sends a valid graph description for Animated.add nodes', () => {
|
||||
const first = new Animated.Value(1);
|
||||
const second = new Animated.Value(2);
|
||||
first.__makeNative();
|
||||
second.__makeNative();
|
||||
|
||||
createAndMountComponent(Animated.View, {
|
||||
style: {
|
||||
opacity: Animated.add(first, second),
|
||||
},
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
||||
jasmine.any(Number),
|
||||
{type: 'addition', input: jasmine.any(Array)},
|
||||
);
|
||||
const additionCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter(
|
||||
(call) => call[1].type === 'addition'
|
||||
);
|
||||
expect(additionCalls.length).toBe(1);
|
||||
const additionCall = additionCalls[0];
|
||||
const additionNodeTag = additionCall[0];
|
||||
const additionConnectionCalls = nativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
|
||||
(call) => call[1] === additionNodeTag
|
||||
);
|
||||
expect(additionConnectionCalls.length).toBe(2);
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(additionCall[1].input[0], {type: 'value', value: 1, offset: 0});
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(additionCall[1].input[1], {type: 'value', value: 2, offset: 0});
|
||||
});
|
||||
|
||||
it('sends a valid graph description for Animated.multiply nodes', () => {
|
||||
const first = new Animated.Value(2);
|
||||
const second = new Animated.Value(1);
|
||||
first.__makeNative();
|
||||
second.__makeNative();
|
||||
|
||||
createAndMountComponent(Animated.View, {
|
||||
style: {
|
||||
opacity: Animated.multiply(first, second),
|
||||
},
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
||||
jasmine.any(Number),
|
||||
{type: 'multiplication', input: jasmine.any(Array)},
|
||||
);
|
||||
const multiplicationCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter(
|
||||
(call) => call[1].type === 'multiplication'
|
||||
);
|
||||
expect(multiplicationCalls.length).toBe(1);
|
||||
const multiplicationCall = multiplicationCalls[0];
|
||||
const multiplicationNodeTag = multiplicationCall[0];
|
||||
const multiplicationConnectionCalls = nativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
|
||||
(call) => call[1] === multiplicationNodeTag
|
||||
);
|
||||
expect(multiplicationConnectionCalls.length).toBe(2);
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(multiplicationCall[1].input[0], {type: 'value', value: 2, offset: 0});
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(multiplicationCall[1].input[1], {type: 'value', value: 1, offset: 0});
|
||||
});
|
||||
|
||||
it('sends a valid graph description for Animated.divide nodes', () => {
|
||||
const first = new Animated.Value(4);
|
||||
const second = new Animated.Value(2);
|
||||
first.__makeNative();
|
||||
second.__makeNative();
|
||||
|
||||
createAndMountComponent(Animated.View, {
|
||||
style: {
|
||||
opacity: Animated.divide(first, second),
|
||||
},
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(jasmine.any(Number), {type: 'division', input: jasmine.any(Array)});
|
||||
const divisionCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter(
|
||||
(call) => call[1].type === 'division'
|
||||
);
|
||||
expect(divisionCalls.length).toBe(1);
|
||||
const divisionCall = divisionCalls[0];
|
||||
const divisionNodeTag = divisionCall[0];
|
||||
const divisionConnectionCalls = nativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
|
||||
(call) => call[1] === divisionNodeTag
|
||||
);
|
||||
expect(divisionConnectionCalls.length).toBe(2);
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(divisionCall[1].input[0], {type: 'value', value: 4, offset: 0});
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(divisionCall[1].input[1], {type: 'value', value: 2, offset: 0});
|
||||
});
|
||||
|
||||
it('sends a valid graph description for Animated.modulo nodes', () => {
|
||||
const value = new Animated.Value(4);
|
||||
value.__makeNative();
|
||||
|
||||
createAndMountComponent(Animated.View, {
|
||||
style: {
|
||||
opacity: Animated.modulo(value, 4),
|
||||
},
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
||||
jasmine.any(Number),
|
||||
{type: 'modulus', modulus: 4, input: jasmine.any(Number)},
|
||||
);
|
||||
const moduloCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter(
|
||||
(call) => call[1].type === 'modulus'
|
||||
);
|
||||
expect(moduloCalls.length).toBe(1);
|
||||
const moduloCall = moduloCalls[0];
|
||||
const moduloNodeTag = moduloCall[0];
|
||||
const moduloConnectionCalls = nativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
|
||||
(call) => call[1] === moduloNodeTag
|
||||
);
|
||||
expect(moduloConnectionCalls.length).toBe(1);
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(moduloCall[1].input, {type: 'value', value: 4, offset: 0});
|
||||
});
|
||||
|
||||
it('sends a valid graph description for interpolate() nodes', () => {
|
||||
const value = new Animated.Value(10);
|
||||
value.__makeNative();
|
||||
|
||||
createAndMountComponent(Animated.View, {
|
||||
style: {
|
||||
opacity: value.interpolate({
|
||||
inputRange: [10, 20],
|
||||
outputRange: [0, 1],
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
||||
jasmine.any(Number),
|
||||
{type: 'value', value: 10, offset: 0}
|
||||
);
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(jasmine.any(Number), {
|
||||
type: 'interpolation',
|
||||
inputRange: [10, 20],
|
||||
outputRange: [0, 1],
|
||||
extrapolateLeft: 'extend',
|
||||
extrapolateRight: 'extend',
|
||||
});
|
||||
const interpolationNodeTag = nativeAnimatedModule.createAnimatedNode.mock.calls.find(
|
||||
(call) => call[1].type === 'interpolation'
|
||||
)[0];
|
||||
const valueNodeTag = nativeAnimatedModule.createAnimatedNode.mock.calls.find(
|
||||
(call) => call[1].type === 'value'
|
||||
)[0];
|
||||
expect(nativeAnimatedModule.connectAnimatedNodes).toBeCalledWith(valueNodeTag, interpolationNodeTag);
|
||||
});
|
||||
|
||||
it('sends a valid graph description for transform nodes', () => {
|
||||
const value = new Animated.Value(0);
|
||||
value.__makeNative();
|
||||
|
||||
createAndMountComponent(Animated.View, {
|
||||
style: {
|
||||
transform: [{translateX: value}, {scale: 2}],
|
||||
},
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
||||
jasmine.any(Number),
|
||||
{
|
||||
type: 'transform',
|
||||
transforms: [{
|
||||
nodeTag: jasmine.any(Number),
|
||||
property: 'translateX',
|
||||
type: 'animated',
|
||||
}, {
|
||||
value: 2,
|
||||
property: 'scale',
|
||||
type: 'static',
|
||||
}],
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('sends a valid graph description for Animated.diffClamp nodes', () => {
|
||||
const value = new Animated.Value(2);
|
||||
value.__makeNative();
|
||||
|
||||
createAndMountComponent(Animated.View, {
|
||||
style: {
|
||||
opacity: Animated.diffClamp(value, 0, 20),
|
||||
},
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).toBeCalledWith(
|
||||
jasmine.any(Number),
|
||||
{type: 'diffclamp', input: jasmine.any(Number), max: 20, min: 0},
|
||||
);
|
||||
const diffClampCalls = nativeAnimatedModule.createAnimatedNode.mock.calls.filter(
|
||||
(call) => call[1].type === 'diffclamp'
|
||||
);
|
||||
expect(diffClampCalls.length).toBe(1);
|
||||
const diffClampCall = diffClampCalls[0];
|
||||
const diffClampNodeTag = diffClampCall[0];
|
||||
const diffClampConnectionCalls = nativeAnimatedModule.connectAnimatedNodes.mock.calls.filter(
|
||||
(call) => call[1] === diffClampNodeTag
|
||||
);
|
||||
expect(diffClampConnectionCalls.length).toBe(1);
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(diffClampCall[1].input, {type: 'value', value: 2, offset: 0});
|
||||
});
|
||||
|
||||
it('doesn\'t call into native API if useNativeDriver is set to false', () => {
|
||||
const anim = new Animated.Value(0);
|
||||
|
||||
const c = createAndMountComponent(Animated.View, {
|
||||
style: {
|
||||
opacity: anim,
|
||||
},
|
||||
});
|
||||
|
||||
Animated.timing(anim, {toValue: 10, duration: 1000, useNativeDriver: false}).start();
|
||||
|
||||
c.componentWillUnmount();
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode).not.toBeCalled();
|
||||
});
|
||||
|
||||
it('fails when trying to run non-native animation on native node', () => {
|
||||
const anim = new Animated.Value(0);
|
||||
|
||||
createAndMountComponent(Animated.View, {
|
||||
style: {
|
||||
opacity: anim,
|
||||
},
|
||||
});
|
||||
|
||||
Animated.timing(anim, {toValue: 10, duration: 50, useNativeDriver: true}).start();
|
||||
jest.runAllTimers();
|
||||
|
||||
Animated.timing(anim, {toValue: 4, duration: 500, useNativeDriver: false}).start();
|
||||
expect(jest.runAllTimers).toThrow();
|
||||
});
|
||||
|
||||
it('fails for unsupported styles', () => {
|
||||
const anim = new Animated.Value(0);
|
||||
|
||||
createAndMountComponent(Animated.View, {
|
||||
style: {
|
||||
left: anim,
|
||||
},
|
||||
});
|
||||
|
||||
const animation = Animated.timing(anim, {toValue: 10, duration: 50, useNativeDriver: true});
|
||||
expect(animation.start).toThrowError(/left/);
|
||||
});
|
||||
|
||||
it('works for any `static` props and styles', () => {
|
||||
// Passing "unsupported" props should work just fine as long as they are not animated
|
||||
const value = new Animated.Value(0);
|
||||
value.__makeNative();
|
||||
|
||||
createAndMountComponent(Animated.View, {
|
||||
style: {
|
||||
left: 10,
|
||||
top: 20,
|
||||
opacity: value,
|
||||
},
|
||||
removeClippedSubviews: true,
|
||||
});
|
||||
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(jasmine.any(Number), { type: 'style', style: { opacity: jasmine.any(Number) }});
|
||||
expect(nativeAnimatedModule.createAnimatedNode)
|
||||
.toBeCalledWith(jasmine.any(Number), { type: 'props', props: { style: jasmine.any(Number) }});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Animations', () => {
|
||||
it('sends a valid timing animation description', () => {
|
||||
const anim = new Animated.Value(0);
|
||||
Animated.timing(anim, {toValue: 10, duration: 1000, useNativeDriver: true}).start();
|
||||
|
||||
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
||||
jasmine.any(Number),
|
||||
jasmine.any(Number),
|
||||
{type: 'frames', frames: jasmine.any(Array), toValue: jasmine.any(Number), iterations: 1},
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it('sends a valid spring animation description', () => {
|
||||
const anim = new Animated.Value(0);
|
||||
Animated.spring(anim, {toValue: 10, friction: 5, tension: 164, useNativeDriver: true}).start();
|
||||
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
||||
jasmine.any(Number),
|
||||
jasmine.any(Number),
|
||||
{
|
||||
type: 'spring',
|
||||
friction: 16,
|
||||
initialVelocity: 0,
|
||||
overshootClamping: false,
|
||||
restDisplacementThreshold: 0.001,
|
||||
restSpeedThreshold: 0.001,
|
||||
tension: 679.08,
|
||||
toValue: 10,
|
||||
iterations: 1,
|
||||
},
|
||||
jasmine.any(Function)
|
||||
);
|
||||
|
||||
Animated.spring(anim, {toValue: 10, bounciness: 8, speed: 10, useNativeDriver: true}).start();
|
||||
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
||||
jasmine.any(Number),
|
||||
jasmine.any(Number),
|
||||
{
|
||||
type: 'spring',
|
||||
friction: 23.05223140901191,
|
||||
initialVelocity: 0,
|
||||
overshootClamping: false,
|
||||
restDisplacementThreshold: 0.001,
|
||||
restSpeedThreshold: 0.001,
|
||||
tension: 299.61882352941177,
|
||||
toValue: 10,
|
||||
iterations: 1,
|
||||
},
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it('sends a valid decay animation description', () => {
|
||||
const anim = new Animated.Value(0);
|
||||
Animated.decay(anim, {velocity: 10, deceleration: 0.1, useNativeDriver: true}).start();
|
||||
|
||||
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
||||
jasmine.any(Number),
|
||||
jasmine.any(Number),
|
||||
{type: 'decay', deceleration: 0.1, velocity: 10, iterations: 1},
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it('works with Animated.loop', () => {
|
||||
const anim = new Animated.Value(0);
|
||||
Animated.loop(
|
||||
Animated.decay(anim, {velocity: 10, deceleration: 0.1, useNativeDriver: true}),
|
||||
{ iterations: 10 },
|
||||
).start();
|
||||
|
||||
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
||||
jasmine.any(Number),
|
||||
jasmine.any(Number),
|
||||
{type: 'decay', deceleration: 0.1, velocity: 10, iterations: 10},
|
||||
jasmine.any(Function)
|
||||
);
|
||||
});
|
||||
|
||||
it('sends stopAnimation command to native', () => {
|
||||
const value = new Animated.Value(0);
|
||||
const animation = Animated.timing(value, {toValue: 10, duration: 50, useNativeDriver: true});
|
||||
|
||||
animation.start();
|
||||
expect(nativeAnimatedModule.startAnimatingNode).toBeCalledWith(
|
||||
jasmine.any(Number),
|
||||
jasmine.any(Number),
|
||||
{type: 'frames', frames: jasmine.any(Array), toValue: jasmine.any(Number), iterations: 1},
|
||||
jasmine.any(Function)
|
||||
);
|
||||
const animationId = nativeAnimatedModule.startAnimatingNode.mock.calls[0][0];
|
||||
|
||||
animation.stop();
|
||||
expect(nativeAnimatedModule.stopAnimation).toBeCalledWith(animationId);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,125 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var Easing = require('Easing');
|
||||
describe('Easing', () => {
|
||||
it('should work with linear', () => {
|
||||
var easing = Easing.linear;
|
||||
|
||||
expect(easing(0)).toBe(0);
|
||||
expect(easing(0.5)).toBe(0.5);
|
||||
expect(easing(0.8)).toBe(0.8);
|
||||
expect(easing(1)).toBe(1);
|
||||
});
|
||||
|
||||
it('should work with ease in linear', () => {
|
||||
var easing = Easing.in(Easing.linear);
|
||||
expect(easing(0)).toBe(0);
|
||||
expect(easing(0.5)).toBe(0.5);
|
||||
expect(easing(0.8)).toBe(0.8);
|
||||
expect(easing(1)).toBe(1);
|
||||
});
|
||||
|
||||
it('should work with easy out linear', () => {
|
||||
var easing = Easing.out(Easing.linear);
|
||||
expect(easing(0)).toBe(0);
|
||||
expect(easing(0.5)).toBe(0.5);
|
||||
expect(easing(0.6)).toBe(0.6);
|
||||
expect(easing(1)).toBe(1);
|
||||
});
|
||||
|
||||
it('should work with ease in quad', () => {
|
||||
function easeInQuad(t) {
|
||||
return t * t;
|
||||
}
|
||||
var easing = Easing.in(Easing.quad);
|
||||
for (var t = -0.5; t < 1.5; t += 0.1) {
|
||||
expect(easing(t)).toBe(easeInQuad(t));
|
||||
}
|
||||
});
|
||||
|
||||
it('should work with ease out quad', () => {
|
||||
function easeOutQuad(t) {
|
||||
return -t * (t - 2);
|
||||
}
|
||||
var easing = Easing.out(Easing.quad);
|
||||
for (var t = 0; t <= 1; t += 0.1) {
|
||||
expect(easing(1)).toBe(easeOutQuad(1));
|
||||
}
|
||||
});
|
||||
|
||||
it('should work with ease in-out quad', () => {
|
||||
function easeInOutQuad(t) {
|
||||
t = t * 2;
|
||||
if (t < 1) {
|
||||
return 0.5 * t * t;
|
||||
}
|
||||
return -((t - 1) * (t - 3) - 1) / 2;
|
||||
}
|
||||
var easing = Easing.inOut(Easing.quad);
|
||||
for (var t = -0.5; t < 1.5; t += 0.1) {
|
||||
expect(easing(t)).toBeCloseTo(easeInOutQuad(t), 4);
|
||||
}
|
||||
});
|
||||
|
||||
it('should satisfy boundary conditions with elastic', () => {
|
||||
for (var b = 0; b < 4; b += 0.3) {
|
||||
var easing = Easing.elastic(b);
|
||||
expect(easing(0)).toBe(0);
|
||||
expect(easing(1)).toBe(1);
|
||||
}
|
||||
});
|
||||
|
||||
function sampleEasingFunction(easing) {
|
||||
var DURATION = 300;
|
||||
var tickCount = Math.round(DURATION * 60 / 1000);
|
||||
var samples = [];
|
||||
for (var i = 0; i <= tickCount; i++) {
|
||||
samples.push(easing(i / tickCount));
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
|
||||
var Samples = {
|
||||
in_quad: [0,0.0030864197530864196,0.012345679012345678,0.027777777777777776,0.04938271604938271,0.0771604938271605,0.1111111111111111,0.15123456790123457,0.19753086419753085,0.25,0.308641975308642,0.37345679012345684,0.4444444444444444,0.5216049382716049,0.6049382716049383,0.6944444444444445,0.7901234567901234,0.8919753086419753,1],
|
||||
out_quad: [0,0.10802469135802469,0.20987654320987653,0.3055555555555555,0.3950617283950617,0.47839506172839513,0.5555555555555556,0.6265432098765432,0.691358024691358,0.75,0.8024691358024691,0.8487654320987654,0.888888888888889,0.9228395061728394,0.9506172839506174,0.9722222222222221,0.9876543209876543,0.9969135802469136,1],
|
||||
inOut_quad: [0,0.006172839506172839,0.024691358024691357,0.05555555555555555,0.09876543209876543,0.154320987654321,0.2222222222222222,0.30246913580246915,0.3950617283950617,0.5,0.6049382716049383,0.697530864197531,0.7777777777777777,0.845679012345679,0.9012345679012346,0.9444444444444444,0.9753086419753086,0.9938271604938271,1],
|
||||
in_cubic: [0,0.00017146776406035664,0.0013717421124828531,0.004629629629629629,0.010973936899862825,0.021433470507544586,0.037037037037037035,0.05881344307270234,0.0877914951989026,0.125,0.1714677640603567,0.22822359396433475,0.2962962962962963,0.37671467764060357,0.4705075445816187,0.5787037037037038,0.7023319615912208,0.8424211248285322,1],
|
||||
out_cubic: [0,0.15757887517146785,0.2976680384087792,0.42129629629629617,0.5294924554183813,0.6232853223593964,0.7037037037037036,0.7717764060356652,0.8285322359396433,0.875,0.9122085048010974,0.9411865569272977,0.9629629629629629,0.9785665294924554,0.9890260631001372,0.9953703703703703,0.9986282578875172,0.9998285322359396,1],
|
||||
inOut_cubic: [0,0.0006858710562414266,0.0054869684499314125,0.018518518518518517,0.0438957475994513,0.08573388203017834,0.14814814814814814,0.23525377229080935,0.3511659807956104,0.5,0.6488340192043895,0.7647462277091908,0.8518518518518519,0.9142661179698217,0.9561042524005487,0.9814814814814815,0.9945130315500685,0.9993141289437586,1],
|
||||
in_sin: [0,0.003805301908254455,0.01519224698779198,0.03407417371093169,0.06030737921409157,0.09369221296335006,0.1339745962155613,0.1808479557110082,0.233955556881022,0.2928932188134524,0.35721239031346064,0.42642356364895384,0.4999999999999999,0.5773817382593005,0.6579798566743311,0.7411809548974793,0.8263518223330696,0.9128442572523416,0.9999999999999999],
|
||||
out_sin: [0,0.08715574274765817,0.17364817766693033,0.25881904510252074,0.3420201433256687,0.42261826174069944,0.49999999999999994,0.573576436351046,0.6427876096865393,0.7071067811865475,0.766044443118978,0.8191520442889918,0.8660254037844386,0.9063077870366499,0.9396926207859083,0.9659258262890683,0.984807753012208,0.9961946980917455,1],
|
||||
inOut_sin: [0,0.00759612349389599,0.030153689607045786,0.06698729810778065,0.116977778440511,0.17860619515673032,0.24999999999999994,0.32898992833716556,0.4131759111665348,0.49999999999999994,0.5868240888334652,0.6710100716628343,0.7499999999999999,0.8213938048432696,0.883022221559489,0.9330127018922194,0.9698463103929542,0.9924038765061041,1],
|
||||
in_exp: [0,0.0014352875901128893,0.002109491677524035,0.0031003926796253885,0.004556754060844206,0.006697218616039631,0.009843133202303688,0.014466792379488908,0.021262343752724643,0.03125,0.045929202883612456,0.06750373368076916,0.09921256574801243,0.1458161299470146,0.2143109957132682,0.31498026247371835,0.46293735614364506,0.6803950000871883,1],
|
||||
out_exp: [0,0.31960499991281155,0.5370626438563548,0.6850197375262816,0.7856890042867318,0.8541838700529854,0.9007874342519875,0.9324962663192309,0.9540707971163875,0.96875,0.9787376562472754,0.9855332076205111,0.9901568667976963,0.9933027813839603,0.9954432459391558,0.9968996073203746,0.9978905083224759,0.9985647124098871,1],
|
||||
inOut_exp: [0,0.0010547458387620175,0.002278377030422103,0.004921566601151844,0.010631171876362321,0.022964601441806228,0.049606282874006216,0.1071554978566341,0.23146867807182253,0.5,0.7685313219281775,0.892844502143366,0.9503937171259937,0.9770353985581938,0.9893688281236377,0.9950784333988482,0.9977216229695779,0.998945254161238,1],
|
||||
in_circle: [0,0.0015444024660317135,0.006192010000093506,0.013986702816730645,0.025003956956430873,0.03935464078941209,0.057190958417936644,0.07871533601238889,0.10419358352238339,0.1339745962155614,0.1685205807169019,0.20845517506805522,0.2546440075000701,0.3083389112228482,0.37146063894529113,0.4472292016074334,0.5418771527091488,0.6713289009389102,1],
|
||||
out_circle: [0,0.3286710990610898,0.45812284729085123,0.5527707983925666,0.6285393610547089,0.6916610887771518,0.7453559924999298,0.7915448249319448,0.8314794192830981,0.8660254037844386,0.8958064164776166,0.9212846639876111,0.9428090415820634,0.9606453592105879,0.9749960430435691,0.9860132971832694,0.9938079899999065,0.9984555975339683,1],
|
||||
inOut_circle: [0,0.003096005000046753,0.012501978478215436,0.028595479208968322,0.052096791761191696,0.08426029035845095,0.12732200375003505,0.18573031947264557,0.2709385763545744,0.5,0.7290614236454256,0.8142696805273546,0.8726779962499649,0.915739709641549,0.9479032082388084,0.9714045207910317,0.9874980215217846,0.9969039949999532,1],
|
||||
in_back_: [0,-0.004788556241426612,-0.017301289437585736,-0.0347587962962963,-0.05438167352537723,-0.07339051783264748,-0.08900592592592595,-0.09844849451303156,-0.0989388203017833,-0.08769750000000004,-0.06194513031550073,-0.018902307956104283,0.044210370370370254,0.13017230795610413,0.2417629080932785,0.3817615740740742,0.5529477091906719,0.7581007167352535,0.9999999999999998],
|
||||
out_back_: [2.220446049250313e-16,0.24189928326474652,0.44705229080932807,0.6182384259259258,0.7582370919067215,0.8698276920438959,0.9557896296296297,1.0189023079561044,1.0619451303155008,1.0876975,1.0989388203017834,1.0984484945130315,1.089005925925926,1.0733905178326475,1.0543816735253773,1.0347587962962963,1.0173012894375857,1.0047885562414267,1],
|
||||
};
|
||||
|
||||
Object.keys(Samples).forEach(function(type) {
|
||||
it('should ease ' + type, function() {
|
||||
var [modeName, easingName, isFunction] = type.split('_');
|
||||
var easing = Easing[easingName];
|
||||
if (isFunction !== undefined) {
|
||||
easing = easing();
|
||||
}
|
||||
var computed = sampleEasingFunction(Easing[modeName](easing));
|
||||
var samples = Samples[type];
|
||||
|
||||
computed.forEach((value, key) => {
|
||||
expect(value).toBeCloseTo(samples[key], 2);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,314 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var AnimatedInterpolation = require('../nodes/AnimatedInterpolation');
|
||||
var Easing = require('Easing');
|
||||
|
||||
describe('Interpolation', () => {
|
||||
it('should work with defaults', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1],
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe(0);
|
||||
expect(interpolation(0.5)).toBe(0.5);
|
||||
expect(interpolation(0.8)).toBe(0.8);
|
||||
expect(interpolation(1)).toBe(1);
|
||||
});
|
||||
|
||||
it('should work with output range', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [100, 200],
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe(100);
|
||||
expect(interpolation(0.5)).toBe(150);
|
||||
expect(interpolation(0.8)).toBe(180);
|
||||
expect(interpolation(1)).toBe(200);
|
||||
});
|
||||
|
||||
it('should work with input range', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [100, 200],
|
||||
outputRange: [0, 1],
|
||||
});
|
||||
|
||||
expect(interpolation(100)).toBe(0);
|
||||
expect(interpolation(150)).toBe(0.5);
|
||||
expect(interpolation(180)).toBe(0.8);
|
||||
expect(interpolation(200)).toBe(1);
|
||||
});
|
||||
|
||||
it('should throw for non monotonic input ranges', () => {
|
||||
expect(() =>
|
||||
AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 2, 1],
|
||||
outputRange: [0, 1, 2],
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
expect(() =>
|
||||
AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: [0, 3, 1],
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should work with empty input range', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 10, 10],
|
||||
outputRange: [1, 2, 3],
|
||||
extrapolate: 'extend',
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe(1);
|
||||
expect(interpolation(5)).toBe(1.5);
|
||||
expect(interpolation(10)).toBe(2);
|
||||
expect(interpolation(10.1)).toBe(3);
|
||||
expect(interpolation(15)).toBe(3);
|
||||
});
|
||||
|
||||
it('should work with empty output range', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [1, 2, 3],
|
||||
outputRange: [0, 10, 10],
|
||||
extrapolate: 'extend',
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe(-10);
|
||||
expect(interpolation(1.5)).toBe(5);
|
||||
expect(interpolation(2)).toBe(10);
|
||||
expect(interpolation(2.5)).toBe(10);
|
||||
expect(interpolation(3)).toBe(10);
|
||||
expect(interpolation(4)).toBe(10);
|
||||
});
|
||||
|
||||
it('should work with easing', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1],
|
||||
easing: Easing.quad,
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe(0);
|
||||
expect(interpolation(0.5)).toBe(0.25);
|
||||
expect(interpolation(0.9)).toBe(0.81);
|
||||
expect(interpolation(1)).toBe(1);
|
||||
});
|
||||
|
||||
it('should work with extrapolate', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1],
|
||||
extrapolate: 'extend',
|
||||
easing: Easing.quad,
|
||||
});
|
||||
|
||||
expect(interpolation(-2)).toBe(4);
|
||||
expect(interpolation(2)).toBe(4);
|
||||
|
||||
interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1],
|
||||
extrapolate: 'clamp',
|
||||
easing: Easing.quad,
|
||||
});
|
||||
|
||||
expect(interpolation(-2)).toBe(0);
|
||||
expect(interpolation(2)).toBe(1);
|
||||
|
||||
interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1],
|
||||
extrapolate: 'identity',
|
||||
easing: Easing.quad,
|
||||
});
|
||||
|
||||
expect(interpolation(-2)).toBe(-2);
|
||||
expect(interpolation(2)).toBe(2);
|
||||
});
|
||||
|
||||
it('should work with keyframes with extrapolate', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 10, 100, 1000],
|
||||
outputRange: [0, 5, 50, 500],
|
||||
extrapolate: true,
|
||||
});
|
||||
|
||||
expect(interpolation(-5)).toBe(-2.5);
|
||||
expect(interpolation(0)).toBe(0);
|
||||
expect(interpolation(5)).toBe(2.5);
|
||||
expect(interpolation(10)).toBe(5);
|
||||
expect(interpolation(50)).toBe(25);
|
||||
expect(interpolation(100)).toBe(50);
|
||||
expect(interpolation(500)).toBe(250);
|
||||
expect(interpolation(1000)).toBe(500);
|
||||
expect(interpolation(2000)).toBe(1000);
|
||||
});
|
||||
|
||||
it('should work with keyframes without extrapolate', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: [0.2, 1, 0.2],
|
||||
extrapolate: 'clamp',
|
||||
});
|
||||
|
||||
expect(interpolation(5)).toBeCloseTo(0.2);
|
||||
});
|
||||
|
||||
it('should throw for an infinite input range', () => {
|
||||
expect(() =>
|
||||
AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [-Infinity, Infinity],
|
||||
outputRange: [0, 1],
|
||||
}),
|
||||
).toThrow();
|
||||
|
||||
expect(() =>
|
||||
AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [-Infinity, 0, Infinity],
|
||||
outputRange: [1, 2, 3],
|
||||
}),
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('should work with negative infinite', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [-Infinity, 0],
|
||||
outputRange: [-Infinity, 0],
|
||||
easing: Easing.quad,
|
||||
extrapolate: 'identity',
|
||||
});
|
||||
|
||||
expect(interpolation(-Infinity)).toBe(-Infinity);
|
||||
expect(interpolation(-100)).toBeCloseTo(-10000);
|
||||
expect(interpolation(-10)).toBeCloseTo(-100);
|
||||
expect(interpolation(0)).toBeCloseTo(0);
|
||||
expect(interpolation(1)).toBeCloseTo(1);
|
||||
expect(interpolation(100)).toBeCloseTo(100);
|
||||
});
|
||||
|
||||
it('should work with positive infinite', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [5, Infinity],
|
||||
outputRange: [5, Infinity],
|
||||
easing: Easing.quad,
|
||||
extrapolate: 'identity',
|
||||
});
|
||||
|
||||
expect(interpolation(-100)).toBeCloseTo(-100);
|
||||
expect(interpolation(-10)).toBeCloseTo(-10);
|
||||
expect(interpolation(0)).toBeCloseTo(0);
|
||||
expect(interpolation(5)).toBeCloseTo(5);
|
||||
expect(interpolation(6)).toBeCloseTo(5 + 1);
|
||||
expect(interpolation(10)).toBeCloseTo(5 + 25);
|
||||
expect(interpolation(100)).toBeCloseTo(5 + 95 * 95);
|
||||
expect(interpolation(Infinity)).toBe(Infinity);
|
||||
});
|
||||
|
||||
it('should work with output ranges as string', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.4)'],
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe('rgba(0, 100, 200, 0)');
|
||||
expect(interpolation(0.5)).toBe('rgba(25, 125, 225, 0.2)');
|
||||
expect(interpolation(1)).toBe('rgba(50, 150, 250, 0.4)');
|
||||
});
|
||||
|
||||
it('should work with output ranges as short hex string', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['#024', '#9BF'],
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe('rgba(0, 34, 68, 1)');
|
||||
expect(interpolation(0.5)).toBe('rgba(77, 111, 162, 1)');
|
||||
expect(interpolation(1)).toBe('rgba(153, 187, 255, 1)');
|
||||
});
|
||||
|
||||
it('should work with output ranges as long hex string', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['#FF9500', '#87FC70'],
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe('rgba(255, 149, 0, 1)');
|
||||
expect(interpolation(0.5)).toBe('rgba(195, 201, 56, 1)');
|
||||
expect(interpolation(1)).toBe('rgba(135, 252, 112, 1)');
|
||||
});
|
||||
|
||||
it('should work with output ranges with mixed hex and rgba strings', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['rgba(100, 120, 140, .4)', '#87FC70'],
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe('rgba(100, 120, 140, 0.4)');
|
||||
expect(interpolation(0.5)).toBe('rgba(118, 186, 126, 0.7)');
|
||||
expect(interpolation(1)).toBe('rgba(135, 252, 112, 1)');
|
||||
});
|
||||
|
||||
it('should work with negative and decimal values in string ranges', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['-100.5deg', '100deg'],
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe('-100.5deg');
|
||||
expect(interpolation(0.5)).toBe('-0.25deg');
|
||||
expect(interpolation(1)).toBe('100deg');
|
||||
});
|
||||
|
||||
it('should crash when chaining an interpolation that returns a string', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: [0, 1],
|
||||
});
|
||||
expect(() => {
|
||||
interpolation('45rad');
|
||||
}).toThrow();
|
||||
});
|
||||
|
||||
it('should support a mix of color patterns', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1, 2],
|
||||
outputRange: ['rgba(0, 100, 200, 0)', 'rgb(50, 150, 250)', 'red'],
|
||||
});
|
||||
|
||||
expect(interpolation(0)).toBe('rgba(0, 100, 200, 0)');
|
||||
expect(interpolation(0.5)).toBe('rgba(25, 125, 225, 0.5)');
|
||||
expect(interpolation(1.5)).toBe('rgba(153, 75, 125, 1)');
|
||||
expect(interpolation(2)).toBe('rgba(255, 0, 0, 1)');
|
||||
});
|
||||
|
||||
it('should crash when defining output range with different pattern', () => {
|
||||
expect(() =>
|
||||
AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['20deg', '30rad'],
|
||||
}),
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
it('should round the alpha channel of a color to the nearest thousandth', () => {
|
||||
var interpolation = AnimatedInterpolation.__createInterpolation({
|
||||
inputRange: [0, 1],
|
||||
outputRange: ['rgba(0, 0, 0, 0)', 'rgba(0, 0, 0, 1)'],
|
||||
});
|
||||
|
||||
expect(interpolation(1e-12)).toBe('rgba(0, 0, 0, 0)');
|
||||
expect(interpolation(2 / 3)).toBe('rgba(0, 0, 0, 0.667)');
|
||||
});
|
||||
});
|
||||
@@ -1,105 +0,0 @@
|
||||
/**
|
||||
* BezierEasing - use bezier curve for transition easing function
|
||||
* https://github.com/gre/bezier-easing
|
||||
*
|
||||
* @copyright 2014-2015 Gaëtan Renaudeau. MIT License.
|
||||
* @noflow
|
||||
*/
|
||||
|
||||
/* eslint-disable */
|
||||
|
||||
'use strict';
|
||||
|
||||
var bezier = require('bezier');
|
||||
|
||||
var identity = function (x) { return x; };
|
||||
|
||||
function assertClose (a, b, precision = 3) {
|
||||
expect(a).toBeCloseTo(b, precision);
|
||||
}
|
||||
|
||||
function makeAssertCloseWithPrecision (precision) {
|
||||
return function (a, b) {
|
||||
assertClose(a, b, precision);
|
||||
};
|
||||
}
|
||||
|
||||
function allEquals (be1, be2, samples, assertion) {
|
||||
if (!assertion) assertion = assertClose;
|
||||
for (var i=0; i<=samples; ++i) {
|
||||
var x = i / samples;
|
||||
assertion(be1(x), be2(x));
|
||||
}
|
||||
}
|
||||
|
||||
function repeat (n) {
|
||||
return function (f) {
|
||||
for (var i=0; i<n; ++i) f(i);
|
||||
};
|
||||
}
|
||||
|
||||
describe('bezier', function(){
|
||||
it('should be a function', function(){
|
||||
expect(typeof bezier === 'function').toBe(true);
|
||||
});
|
||||
it('should creates an object', function(){
|
||||
expect(typeof bezier(0, 0, 1, 1) === 'function').toBe(true);
|
||||
});
|
||||
it('should fail with wrong arguments', function () {
|
||||
expect(function () { bezier(0.5, 0.5, -5, 0.5); }).toThrow();
|
||||
expect(function () { bezier(0.5, 0.5, 5, 0.5); }).toThrow();
|
||||
expect(function () { bezier(-2, 0.5, 0.5, 0.5); }).toThrow();
|
||||
expect(function () { bezier(2, 0.5, 0.5, 0.5); }).toThrow();
|
||||
});
|
||||
describe('linear curves', function () {
|
||||
it('should be linear', function () {
|
||||
allEquals(bezier(0, 0, 1, 1), bezier(1, 1, 0, 0), 100);
|
||||
allEquals(bezier(0, 0, 1, 1), identity, 100);
|
||||
});
|
||||
});
|
||||
describe('common properties', function () {
|
||||
it('should be the right value at extremes', function () {
|
||||
repeat(10)(function () {
|
||||
var a = Math.random(), b = 2*Math.random()-0.5, c = Math.random(), d = 2*Math.random()-0.5;
|
||||
var easing = bezier(a, b, c, d);
|
||||
expect(easing(0)).toBe(0);
|
||||
expect(easing(1)).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
it('should approach the projected value of its x=y projected curve', function () {
|
||||
repeat(10)(function () {
|
||||
var a = Math.random(), b = Math.random(), c = Math.random(), d = Math.random();
|
||||
var easing = bezier(a, b, c, d);
|
||||
var projected = bezier(b, a, d, c);
|
||||
var composed = function (x) { return projected(easing(x)); };
|
||||
allEquals(identity, composed, 100, makeAssertCloseWithPrecision(2));
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('two same instances', function () {
|
||||
it('should be strictly equals', function () {
|
||||
repeat(10)(function () {
|
||||
var a = Math.random(), b = 2*Math.random()-0.5, c = Math.random(), d = 2*Math.random()-0.5;
|
||||
allEquals(bezier(a, b, c, d), bezier(a, b, c, d), 100, 0);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('symetric curves', function () {
|
||||
it('should have a central value y~=0.5 at x=0.5', function () {
|
||||
repeat(10)(function () {
|
||||
var a = Math.random(), b = 2*Math.random()-0.5, c = 1-a, d = 1-b;
|
||||
var easing = bezier(a, b, c, d);
|
||||
assertClose(easing(0.5), 0.5, 2);
|
||||
});
|
||||
});
|
||||
it('should be symetrical', function () {
|
||||
repeat(10)(function () {
|
||||
var a = Math.random(), b = 2*Math.random()-0.5, c = 1-a, d = 1-b;
|
||||
var easing = bezier(a, b, c, d);
|
||||
var sym = function (x) { return 1 - easing(1-x); };
|
||||
allEquals(easing, sym, 100, makeAssertCloseWithPrecision(2));
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule Animation
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const NativeAnimatedHelper = require('NativeAnimatedHelper');
|
||||
|
||||
import type AnimatedValue from '../nodes/AnimatedValue';
|
||||
|
||||
export type EndResult = {finished: boolean};
|
||||
export type EndCallback = (result: EndResult) => void;
|
||||
|
||||
export type AnimationConfig = {
|
||||
isInteraction?: boolean,
|
||||
useNativeDriver?: boolean,
|
||||
onComplete?: ?EndCallback,
|
||||
iterations?: number,
|
||||
};
|
||||
|
||||
// Important note: start() and stop() will only be called at most once.
|
||||
// Once an animation has been stopped or finished its course, it will
|
||||
// not be reused.
|
||||
class Animation {
|
||||
__active: boolean;
|
||||
__isInteraction: boolean;
|
||||
__nativeId: number;
|
||||
__onEnd: ?EndCallback;
|
||||
__iterations: number;
|
||||
start(
|
||||
fromValue: number,
|
||||
onUpdate: (value: number) => void,
|
||||
onEnd: ?EndCallback,
|
||||
previousAnimation: ?Animation,
|
||||
animatedValue: AnimatedValue,
|
||||
): void {}
|
||||
stop(): void {
|
||||
if (this.__nativeId) {
|
||||
NativeAnimatedHelper.API.stopAnimation(this.__nativeId);
|
||||
}
|
||||
}
|
||||
__getNativeAnimationConfig(): any {
|
||||
// Subclasses that have corresponding animation implementation done in native
|
||||
// should override this method
|
||||
throw new Error('This animation type cannot be offloaded to native');
|
||||
}
|
||||
// Helper function for subclasses to make sure onEnd is only called once.
|
||||
__debouncedOnEnd(result: EndResult): void {
|
||||
const onEnd = this.__onEnd;
|
||||
this.__onEnd = null;
|
||||
onEnd && onEnd(result);
|
||||
}
|
||||
__startNativeAnimation(animatedValue: AnimatedValue): void {
|
||||
animatedValue.__makeNative();
|
||||
this.__nativeId = NativeAnimatedHelper.generateNewAnimationId();
|
||||
NativeAnimatedHelper.API.startAnimatingNode(
|
||||
this.__nativeId,
|
||||
animatedValue.__getNativeTag(),
|
||||
this.__getNativeAnimationConfig(),
|
||||
this.__debouncedOnEnd.bind(this),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Animation;
|
||||
@@ -1,112 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule DecayAnimation
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const Animation = require('./Animation');
|
||||
|
||||
const {shouldUseNativeDriver} = require('../NativeAnimatedHelper');
|
||||
|
||||
import type {AnimationConfig, EndCallback} from './Animation';
|
||||
import type AnimatedValue from '../nodes/AnimatedValue';
|
||||
|
||||
export type DecayAnimationConfig = AnimationConfig & {
|
||||
velocity: number | {x: number, y: number},
|
||||
deceleration?: number,
|
||||
};
|
||||
|
||||
export type DecayAnimationConfigSingle = AnimationConfig & {
|
||||
velocity: number,
|
||||
deceleration?: number,
|
||||
};
|
||||
|
||||
class DecayAnimation extends Animation {
|
||||
_startTime: number;
|
||||
_lastValue: number;
|
||||
_fromValue: number;
|
||||
_deceleration: number;
|
||||
_velocity: number;
|
||||
_onUpdate: (value: number) => void;
|
||||
_animationFrame: any;
|
||||
_useNativeDriver: boolean;
|
||||
|
||||
constructor(config: DecayAnimationConfigSingle) {
|
||||
super();
|
||||
this._deceleration =
|
||||
config.deceleration !== undefined ? config.deceleration : 0.998;
|
||||
this._velocity = config.velocity;
|
||||
this._useNativeDriver = shouldUseNativeDriver(config);
|
||||
this.__isInteraction =
|
||||
config.isInteraction !== undefined ? config.isInteraction : true;
|
||||
this.__iterations = config.iterations !== undefined ? config.iterations : 1;
|
||||
}
|
||||
|
||||
__getNativeAnimationConfig() {
|
||||
return {
|
||||
type: 'decay',
|
||||
deceleration: this._deceleration,
|
||||
velocity: this._velocity,
|
||||
iterations: this.__iterations,
|
||||
};
|
||||
}
|
||||
|
||||
start(
|
||||
fromValue: number,
|
||||
onUpdate: (value: number) => void,
|
||||
onEnd: ?EndCallback,
|
||||
previousAnimation: ?Animation,
|
||||
animatedValue: AnimatedValue,
|
||||
): void {
|
||||
this.__active = true;
|
||||
this._lastValue = fromValue;
|
||||
this._fromValue = fromValue;
|
||||
this._onUpdate = onUpdate;
|
||||
this.__onEnd = onEnd;
|
||||
this._startTime = Date.now();
|
||||
if (this._useNativeDriver) {
|
||||
this.__startNativeAnimation(animatedValue);
|
||||
} else {
|
||||
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
onUpdate(): void {
|
||||
const now = Date.now();
|
||||
|
||||
const value =
|
||||
this._fromValue +
|
||||
this._velocity /
|
||||
(1 - this._deceleration) *
|
||||
(1 - Math.exp(-(1 - this._deceleration) * (now - this._startTime)));
|
||||
|
||||
this._onUpdate(value);
|
||||
|
||||
if (Math.abs(this._lastValue - value) < 0.1) {
|
||||
this.__debouncedOnEnd({finished: true});
|
||||
return;
|
||||
}
|
||||
|
||||
this._lastValue = value;
|
||||
if (this.__active) {
|
||||
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
super.stop();
|
||||
this.__active = false;
|
||||
global.cancelAnimationFrame(this._animationFrame);
|
||||
this.__debouncedOnEnd({finished: false});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = DecayAnimation;
|
||||
@@ -1,291 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule SpringAnimation
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedValue = require('../nodes/AnimatedValue');
|
||||
const AnimatedValueXY = require('../nodes/AnimatedValueXY');
|
||||
const Animation = require('./Animation');
|
||||
const SpringConfig = require('../SpringConfig');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const {shouldUseNativeDriver} = require('../NativeAnimatedHelper');
|
||||
|
||||
import type {AnimationConfig, EndCallback} from './Animation';
|
||||
|
||||
export type SpringAnimationConfig = AnimationConfig & {
|
||||
toValue: number | AnimatedValue | {x: number, y: number} | AnimatedValueXY,
|
||||
overshootClamping?: boolean,
|
||||
restDisplacementThreshold?: number,
|
||||
restSpeedThreshold?: number,
|
||||
velocity?: number | {x: number, y: number},
|
||||
bounciness?: number,
|
||||
speed?: number,
|
||||
tension?: number,
|
||||
friction?: number,
|
||||
delay?: number,
|
||||
};
|
||||
|
||||
export type SpringAnimationConfigSingle = AnimationConfig & {
|
||||
toValue: number | AnimatedValue,
|
||||
overshootClamping?: boolean,
|
||||
restDisplacementThreshold?: number,
|
||||
restSpeedThreshold?: number,
|
||||
velocity?: number,
|
||||
bounciness?: number,
|
||||
speed?: number,
|
||||
tension?: number,
|
||||
friction?: number,
|
||||
delay?: number,
|
||||
};
|
||||
|
||||
function withDefault<T>(value: ?T, defaultValue: T): T {
|
||||
if (value === undefined || value === null) {
|
||||
return defaultValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
class SpringAnimation extends Animation {
|
||||
_overshootClamping: boolean;
|
||||
_restDisplacementThreshold: number;
|
||||
_restSpeedThreshold: number;
|
||||
_initialVelocity: ?number;
|
||||
_lastVelocity: number;
|
||||
_startPosition: number;
|
||||
_lastPosition: number;
|
||||
_fromValue: number;
|
||||
_toValue: any;
|
||||
_tension: number;
|
||||
_friction: number;
|
||||
_delay: number;
|
||||
_timeout: any;
|
||||
_lastTime: number;
|
||||
_onUpdate: (value: number) => void;
|
||||
_animationFrame: any;
|
||||
_useNativeDriver: boolean;
|
||||
|
||||
constructor(config: SpringAnimationConfigSingle) {
|
||||
super();
|
||||
|
||||
this._overshootClamping = withDefault(config.overshootClamping, false);
|
||||
this._restDisplacementThreshold = withDefault(
|
||||
config.restDisplacementThreshold,
|
||||
0.001,
|
||||
);
|
||||
this._restSpeedThreshold = withDefault(config.restSpeedThreshold, 0.001);
|
||||
this._initialVelocity = config.velocity;
|
||||
this._lastVelocity = withDefault(config.velocity, 0);
|
||||
this._toValue = config.toValue;
|
||||
this._delay = withDefault(config.delay, 0);
|
||||
this._useNativeDriver = shouldUseNativeDriver(config);
|
||||
this.__isInteraction =
|
||||
config.isInteraction !== undefined ? config.isInteraction : true;
|
||||
this.__iterations = config.iterations !== undefined ? config.iterations : 1;
|
||||
|
||||
let springConfig;
|
||||
if (config.bounciness !== undefined || config.speed !== undefined) {
|
||||
invariant(
|
||||
config.tension === undefined && config.friction === undefined,
|
||||
'You can only define bounciness/speed or tension/friction but not both',
|
||||
);
|
||||
springConfig = SpringConfig.fromBouncinessAndSpeed(
|
||||
withDefault(config.bounciness, 8),
|
||||
withDefault(config.speed, 12),
|
||||
);
|
||||
} else {
|
||||
springConfig = SpringConfig.fromOrigamiTensionAndFriction(
|
||||
withDefault(config.tension, 40),
|
||||
withDefault(config.friction, 7),
|
||||
);
|
||||
}
|
||||
this._tension = springConfig.tension;
|
||||
this._friction = springConfig.friction;
|
||||
}
|
||||
|
||||
__getNativeAnimationConfig() {
|
||||
return {
|
||||
type: 'spring',
|
||||
overshootClamping: this._overshootClamping,
|
||||
restDisplacementThreshold: this._restDisplacementThreshold,
|
||||
restSpeedThreshold: this._restSpeedThreshold,
|
||||
tension: this._tension,
|
||||
friction: this._friction,
|
||||
initialVelocity: withDefault(this._initialVelocity, this._lastVelocity),
|
||||
toValue: this._toValue,
|
||||
iterations: this.__iterations,
|
||||
};
|
||||
}
|
||||
|
||||
start(
|
||||
fromValue: number,
|
||||
onUpdate: (value: number) => void,
|
||||
onEnd: ?EndCallback,
|
||||
previousAnimation: ?Animation,
|
||||
animatedValue: AnimatedValue,
|
||||
): void {
|
||||
this.__active = true;
|
||||
this._startPosition = fromValue;
|
||||
this._lastPosition = this._startPosition;
|
||||
|
||||
this._onUpdate = onUpdate;
|
||||
this.__onEnd = onEnd;
|
||||
this._lastTime = Date.now();
|
||||
|
||||
if (previousAnimation instanceof SpringAnimation) {
|
||||
const internalState = previousAnimation.getInternalState();
|
||||
this._lastPosition = internalState.lastPosition;
|
||||
this._lastVelocity = internalState.lastVelocity;
|
||||
this._lastTime = internalState.lastTime;
|
||||
}
|
||||
if (this._initialVelocity !== undefined && this._initialVelocity !== null) {
|
||||
this._lastVelocity = this._initialVelocity;
|
||||
}
|
||||
|
||||
const start = () => {
|
||||
if (this._useNativeDriver) {
|
||||
this.__startNativeAnimation(animatedValue);
|
||||
} else {
|
||||
this.onUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
// If this._delay is more than 0, we start after the timeout.
|
||||
if (this._delay) {
|
||||
this._timeout = setTimeout(start, this._delay);
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
getInternalState(): Object {
|
||||
return {
|
||||
lastPosition: this._lastPosition,
|
||||
lastVelocity: this._lastVelocity,
|
||||
lastTime: this._lastTime,
|
||||
};
|
||||
}
|
||||
|
||||
onUpdate(): void {
|
||||
let position = this._lastPosition;
|
||||
let velocity = this._lastVelocity;
|
||||
|
||||
let tempPosition = this._lastPosition;
|
||||
let tempVelocity = this._lastVelocity;
|
||||
|
||||
// If for some reason we lost a lot of frames (e.g. process large payload or
|
||||
// stopped in the debugger), we only advance by 4 frames worth of
|
||||
// computation and will continue on the next frame. It's better to have it
|
||||
// running at faster speed than jumping to the end.
|
||||
const MAX_STEPS = 64;
|
||||
let now = Date.now();
|
||||
if (now > this._lastTime + MAX_STEPS) {
|
||||
now = this._lastTime + MAX_STEPS;
|
||||
}
|
||||
|
||||
// We are using a fixed time step and a maximum number of iterations.
|
||||
// The following post provides a lot of thoughts into how to build this
|
||||
// loop: http://gafferongames.com/game-physics/fix-your-timestep/
|
||||
const TIMESTEP_MSEC = 1;
|
||||
const numSteps = Math.floor((now - this._lastTime) / TIMESTEP_MSEC);
|
||||
|
||||
for (let i = 0; i < numSteps; ++i) {
|
||||
// Velocity is based on seconds instead of milliseconds
|
||||
const step = TIMESTEP_MSEC / 1000;
|
||||
|
||||
// This is using RK4. A good blog post to understand how it works:
|
||||
// http://gafferongames.com/game-physics/integration-basics/
|
||||
const aVelocity = velocity;
|
||||
const aAcceleration =
|
||||
this._tension * (this._toValue - tempPosition) -
|
||||
this._friction * tempVelocity;
|
||||
tempPosition = position + aVelocity * step / 2;
|
||||
tempVelocity = velocity + aAcceleration * step / 2;
|
||||
|
||||
const bVelocity = tempVelocity;
|
||||
const bAcceleration =
|
||||
this._tension * (this._toValue - tempPosition) -
|
||||
this._friction * tempVelocity;
|
||||
tempPosition = position + bVelocity * step / 2;
|
||||
tempVelocity = velocity + bAcceleration * step / 2;
|
||||
|
||||
const cVelocity = tempVelocity;
|
||||
const cAcceleration =
|
||||
this._tension * (this._toValue - tempPosition) -
|
||||
this._friction * tempVelocity;
|
||||
tempPosition = position + cVelocity * step / 2;
|
||||
tempVelocity = velocity + cAcceleration * step / 2;
|
||||
|
||||
const dVelocity = tempVelocity;
|
||||
const dAcceleration =
|
||||
this._tension * (this._toValue - tempPosition) -
|
||||
this._friction * tempVelocity;
|
||||
tempPosition = position + cVelocity * step / 2;
|
||||
tempVelocity = velocity + cAcceleration * step / 2;
|
||||
|
||||
const dxdt = (aVelocity + 2 * (bVelocity + cVelocity) + dVelocity) / 6;
|
||||
const dvdt =
|
||||
(aAcceleration + 2 * (bAcceleration + cAcceleration) + dAcceleration) /
|
||||
6;
|
||||
|
||||
position += dxdt * step;
|
||||
velocity += dvdt * step;
|
||||
}
|
||||
|
||||
this._lastTime = now;
|
||||
this._lastPosition = position;
|
||||
this._lastVelocity = velocity;
|
||||
|
||||
this._onUpdate(position);
|
||||
if (!this.__active) {
|
||||
// a listener might have stopped us in _onUpdate
|
||||
return;
|
||||
}
|
||||
|
||||
// Conditions for stopping the spring animation
|
||||
let isOvershooting = false;
|
||||
if (this._overshootClamping && this._tension !== 0) {
|
||||
if (this._startPosition < this._toValue) {
|
||||
isOvershooting = position > this._toValue;
|
||||
} else {
|
||||
isOvershooting = position < this._toValue;
|
||||
}
|
||||
}
|
||||
const isVelocity = Math.abs(velocity) <= this._restSpeedThreshold;
|
||||
let isDisplacement = true;
|
||||
if (this._tension !== 0) {
|
||||
isDisplacement =
|
||||
Math.abs(this._toValue - position) <= this._restDisplacementThreshold;
|
||||
}
|
||||
|
||||
if (isOvershooting || (isVelocity && isDisplacement)) {
|
||||
if (this._tension !== 0) {
|
||||
// Ensure that we end up with a round value
|
||||
this._onUpdate(this._toValue);
|
||||
}
|
||||
|
||||
this.__debouncedOnEnd({finished: true});
|
||||
return;
|
||||
}
|
||||
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
super.stop();
|
||||
this.__active = false;
|
||||
clearTimeout(this._timeout);
|
||||
global.cancelAnimationFrame(this._animationFrame);
|
||||
this.__debouncedOnEnd({finished: false});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SpringAnimation;
|
||||
@@ -1,155 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule TimingAnimation
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedValue = require('../nodes/AnimatedValue');
|
||||
const AnimatedValueXY = require('../nodes/AnimatedValueXY');
|
||||
const Animation = require('./Animation');
|
||||
|
||||
const {shouldUseNativeDriver} = require('../NativeAnimatedHelper');
|
||||
|
||||
import type {AnimationConfig, EndCallback} from './Animation';
|
||||
|
||||
export type TimingAnimationConfig = AnimationConfig & {
|
||||
toValue: number | AnimatedValue | {x: number, y: number} | AnimatedValueXY,
|
||||
easing?: (value: number) => number,
|
||||
duration?: number,
|
||||
delay?: number,
|
||||
};
|
||||
|
||||
export type TimingAnimationConfigSingle = AnimationConfig & {
|
||||
toValue: number | AnimatedValue,
|
||||
easing?: (value: number) => number,
|
||||
duration?: number,
|
||||
delay?: number,
|
||||
};
|
||||
|
||||
let _easeInOut;
|
||||
function easeInOut() {
|
||||
if (!_easeInOut) {
|
||||
const Easing = require('Easing');
|
||||
_easeInOut = Easing.inOut(Easing.ease);
|
||||
}
|
||||
return _easeInOut;
|
||||
}
|
||||
|
||||
class TimingAnimation extends Animation {
|
||||
_startTime: number;
|
||||
_fromValue: number;
|
||||
_toValue: any;
|
||||
_duration: number;
|
||||
_delay: number;
|
||||
_easing: (value: number) => number;
|
||||
_onUpdate: (value: number) => void;
|
||||
_animationFrame: any;
|
||||
_timeout: any;
|
||||
_useNativeDriver: boolean;
|
||||
|
||||
constructor(config: TimingAnimationConfigSingle) {
|
||||
super();
|
||||
this._toValue = config.toValue;
|
||||
this._easing = config.easing !== undefined ? config.easing : easeInOut();
|
||||
this._duration = config.duration !== undefined ? config.duration : 500;
|
||||
this._delay = config.delay !== undefined ? config.delay : 0;
|
||||
this.__iterations = config.iterations !== undefined ? config.iterations : 1;
|
||||
this.__isInteraction =
|
||||
config.isInteraction !== undefined ? config.isInteraction : true;
|
||||
this._useNativeDriver = shouldUseNativeDriver(config);
|
||||
}
|
||||
|
||||
__getNativeAnimationConfig(): any {
|
||||
const frameDuration = 1000.0 / 60.0;
|
||||
const frames = [];
|
||||
for (let dt = 0.0; dt < this._duration; dt += frameDuration) {
|
||||
frames.push(this._easing(dt / this._duration));
|
||||
}
|
||||
frames.push(this._easing(1));
|
||||
return {
|
||||
type: 'frames',
|
||||
frames,
|
||||
toValue: this._toValue,
|
||||
iterations: this.__iterations,
|
||||
};
|
||||
}
|
||||
|
||||
start(
|
||||
fromValue: number,
|
||||
onUpdate: (value: number) => void,
|
||||
onEnd: ?EndCallback,
|
||||
previousAnimation: ?Animation,
|
||||
animatedValue: AnimatedValue,
|
||||
): void {
|
||||
this.__active = true;
|
||||
this._fromValue = fromValue;
|
||||
this._onUpdate = onUpdate;
|
||||
this.__onEnd = onEnd;
|
||||
|
||||
const start = () => {
|
||||
// Animations that sometimes have 0 duration and sometimes do not
|
||||
// still need to use the native driver when duration is 0 so as to
|
||||
// not cause intermixed JS and native animations.
|
||||
if (this._duration === 0 && !this._useNativeDriver) {
|
||||
this._onUpdate(this._toValue);
|
||||
this.__debouncedOnEnd({finished: true});
|
||||
} else {
|
||||
this._startTime = Date.now();
|
||||
if (this._useNativeDriver) {
|
||||
this.__startNativeAnimation(animatedValue);
|
||||
} else {
|
||||
this._animationFrame = requestAnimationFrame(
|
||||
this.onUpdate.bind(this),
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
if (this._delay) {
|
||||
this._timeout = setTimeout(start, this._delay);
|
||||
} else {
|
||||
start();
|
||||
}
|
||||
}
|
||||
|
||||
onUpdate(): void {
|
||||
const now = Date.now();
|
||||
if (now >= this._startTime + this._duration) {
|
||||
if (this._duration === 0) {
|
||||
this._onUpdate(this._toValue);
|
||||
} else {
|
||||
this._onUpdate(
|
||||
this._fromValue + this._easing(1) * (this._toValue - this._fromValue),
|
||||
);
|
||||
}
|
||||
this.__debouncedOnEnd({finished: true});
|
||||
return;
|
||||
}
|
||||
|
||||
this._onUpdate(
|
||||
this._fromValue +
|
||||
this._easing((now - this._startTime) / this._duration) *
|
||||
(this._toValue - this._fromValue),
|
||||
);
|
||||
if (this.__active) {
|
||||
this._animationFrame = requestAnimationFrame(this.onUpdate.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
super.stop();
|
||||
this.__active = false;
|
||||
clearTimeout(this._timeout);
|
||||
global.cancelAnimationFrame(this._animationFrame);
|
||||
this.__debouncedOnEnd({finished: false});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TimingAnimation;
|
||||
@@ -1,108 +0,0 @@
|
||||
/**
|
||||
* BezierEasing - use bezier curve for transition easing function
|
||||
* https://github.com/gre/bezier-easing
|
||||
*
|
||||
* @copyright 2014-2015 Gaëtan Renaudeau. MIT License.
|
||||
* @providesModule bezier
|
||||
* @noflow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
// These values are established by empiricism with tests (tradeoff: performance VS precision)
|
||||
var NEWTON_ITERATIONS = 4;
|
||||
var NEWTON_MIN_SLOPE = 0.001;
|
||||
var SUBDIVISION_PRECISION = 0.0000001;
|
||||
var SUBDIVISION_MAX_ITERATIONS = 10;
|
||||
|
||||
var kSplineTableSize = 11;
|
||||
var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0);
|
||||
|
||||
var float32ArraySupported = typeof Float32Array === 'function';
|
||||
|
||||
function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; }
|
||||
function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; }
|
||||
function C (aA1) { return 3.0 * aA1; }
|
||||
|
||||
// Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
|
||||
function calcBezier (aT, aA1, aA2) { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; }
|
||||
|
||||
// Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
|
||||
function getSlope (aT, aA1, aA2) { return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); }
|
||||
|
||||
function binarySubdivide (aX, aA, aB, mX1, mX2) {
|
||||
var currentX, currentT, i = 0;
|
||||
do {
|
||||
currentT = aA + (aB - aA) / 2.0;
|
||||
currentX = calcBezier(currentT, mX1, mX2) - aX;
|
||||
if (currentX > 0.0) {
|
||||
aB = currentT;
|
||||
} else {
|
||||
aA = currentT;
|
||||
}
|
||||
} while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);
|
||||
return currentT;
|
||||
}
|
||||
|
||||
function newtonRaphsonIterate (aX, aGuessT, mX1, mX2) {
|
||||
for (var i = 0; i < NEWTON_ITERATIONS; ++i) {
|
||||
var currentSlope = getSlope(aGuessT, mX1, mX2);
|
||||
if (currentSlope === 0.0) {
|
||||
return aGuessT;
|
||||
}
|
||||
var currentX = calcBezier(aGuessT, mX1, mX2) - aX;
|
||||
aGuessT -= currentX / currentSlope;
|
||||
}
|
||||
return aGuessT;
|
||||
}
|
||||
|
||||
module.exports = function bezier (mX1, mY1, mX2, mY2) {
|
||||
if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) { // eslint-disable-line yoda
|
||||
throw new Error('bezier x values must be in [0, 1] range');
|
||||
}
|
||||
|
||||
// Precompute samples table
|
||||
var sampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize);
|
||||
if (mX1 !== mY1 || mX2 !== mY2) {
|
||||
for (var i = 0; i < kSplineTableSize; ++i) {
|
||||
sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);
|
||||
}
|
||||
}
|
||||
|
||||
function getTForX (aX) {
|
||||
var intervalStart = 0.0;
|
||||
var currentSample = 1;
|
||||
var lastSample = kSplineTableSize - 1;
|
||||
|
||||
for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) {
|
||||
intervalStart += kSampleStepSize;
|
||||
}
|
||||
--currentSample;
|
||||
|
||||
// Interpolate to provide an initial guess for t
|
||||
var dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]);
|
||||
var guessForT = intervalStart + dist * kSampleStepSize;
|
||||
|
||||
var initialSlope = getSlope(guessForT, mX1, mX2);
|
||||
if (initialSlope >= NEWTON_MIN_SLOPE) {
|
||||
return newtonRaphsonIterate(aX, guessForT, mX1, mX2);
|
||||
} else if (initialSlope === 0.0) {
|
||||
return guessForT;
|
||||
} else {
|
||||
return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2);
|
||||
}
|
||||
}
|
||||
|
||||
return function BezierEasing (x) {
|
||||
if (mX1 === mY1 && mX2 === mY2) {
|
||||
return x; // linear
|
||||
}
|
||||
// Because JavaScript number are imprecise, we should guarantee the extremes are right.
|
||||
if (x === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (x === 1) {
|
||||
return 1;
|
||||
}
|
||||
return calcBezier(getTForX(x), mY1, mY2);
|
||||
};
|
||||
};
|
||||
@@ -1,193 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule createAnimatedComponent
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const {AnimatedEvent} = require('./AnimatedEvent');
|
||||
const AnimatedProps = require('./nodes/AnimatedProps');
|
||||
const React = require('React');
|
||||
const ViewStylePropTypes = require('ViewStylePropTypes');
|
||||
|
||||
function createAnimatedComponent(Component: any): any {
|
||||
class AnimatedComponent extends React.Component<Object> {
|
||||
_component: any;
|
||||
_prevComponent: any;
|
||||
_propsAnimated: AnimatedProps;
|
||||
_eventDetachers: Array<Function> = [];
|
||||
_setComponentRef: Function;
|
||||
|
||||
static __skipSetNativeProps_FOR_TESTS_ONLY = false;
|
||||
|
||||
constructor(props: Object) {
|
||||
super(props);
|
||||
this._setComponentRef = this._setComponentRef.bind(this);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._propsAnimated && this._propsAnimated.__detach();
|
||||
this._detachNativeEvents();
|
||||
}
|
||||
|
||||
setNativeProps(props) {
|
||||
this._component.setNativeProps(props);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
this._attachProps(this.props);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._propsAnimated.setNativeView(this._component);
|
||||
this._attachNativeEvents();
|
||||
}
|
||||
|
||||
_attachNativeEvents() {
|
||||
// Make sure to get the scrollable node for components that implement
|
||||
// `ScrollResponder.Mixin`.
|
||||
const scrollableNode = this._component.getScrollableNode
|
||||
? this._component.getScrollableNode()
|
||||
: this._component;
|
||||
|
||||
for (const key in this.props) {
|
||||
const prop = this.props[key];
|
||||
if (prop instanceof AnimatedEvent && prop.__isNative) {
|
||||
prop.__attach(scrollableNode, key);
|
||||
this._eventDetachers.push(() => prop.__detach(scrollableNode, key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_detachNativeEvents() {
|
||||
this._eventDetachers.forEach(remove => remove());
|
||||
this._eventDetachers = [];
|
||||
}
|
||||
|
||||
_attachProps(nextProps) {
|
||||
const oldPropsAnimated = this._propsAnimated;
|
||||
|
||||
// The system is best designed when setNativeProps is implemented. It is
|
||||
// able to avoid re-rendering and directly set the attributes that
|
||||
// changed. However, setNativeProps can only be implemented on leaf
|
||||
// native components. If you want to animate a composite component, you
|
||||
// need to re-render it. In this case, we have a fallback that uses
|
||||
// forceUpdate.
|
||||
const callback = () => {
|
||||
if (
|
||||
!AnimatedComponent.__skipSetNativeProps_FOR_TESTS_ONLY &&
|
||||
this._component.setNativeProps
|
||||
) {
|
||||
if (!this._propsAnimated.__isNative) {
|
||||
this._component.setNativeProps(
|
||||
this._propsAnimated.__getAnimatedValue(),
|
||||
);
|
||||
} else {
|
||||
throw new Error(
|
||||
'Attempting to run JS driven animation on animated ' +
|
||||
'node that has been moved to "native" earlier by starting an ' +
|
||||
'animation with `useNativeDriver: true`',
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this.forceUpdate();
|
||||
}
|
||||
};
|
||||
|
||||
this._propsAnimated = new AnimatedProps(nextProps, callback);
|
||||
|
||||
// When you call detach, it removes the element from the parent list
|
||||
// of children. If it goes to 0, then the parent also detaches itself
|
||||
// and so on.
|
||||
// An optimization is to attach the new elements and THEN detach the old
|
||||
// ones instead of detaching and THEN attaching.
|
||||
// This way the intermediate state isn't to go to 0 and trigger
|
||||
// this expensive recursive detaching to then re-attach everything on
|
||||
// the very next operation.
|
||||
oldPropsAnimated && oldPropsAnimated.__detach();
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
this._attachProps(newProps);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
if (this._component !== this._prevComponent) {
|
||||
this._propsAnimated.setNativeView(this._component);
|
||||
}
|
||||
if (this._component !== this._prevComponent || prevProps !== this.props) {
|
||||
this._detachNativeEvents();
|
||||
this._attachNativeEvents();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this._propsAnimated.__getValue();
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
ref={this._setComponentRef}
|
||||
// The native driver updates views directly through the UI thread so we
|
||||
// have to make sure the view doesn't get optimized away because it cannot
|
||||
// go through the NativeViewHierachyManager since it operates on the shadow
|
||||
// thread.
|
||||
collapsable={
|
||||
this._propsAnimated.__isNative ? false : props.collapsable
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
_setComponentRef(c) {
|
||||
this._prevComponent = this._component;
|
||||
this._component = c;
|
||||
}
|
||||
|
||||
// A third party library can use getNode()
|
||||
// to get the node reference of the decorated component
|
||||
getNode() {
|
||||
return this._component;
|
||||
}
|
||||
}
|
||||
|
||||
// ReactNative `View.propTypes` have been deprecated in favor of
|
||||
// `ViewPropTypes`. In their place a temporary getter has been added with a
|
||||
// deprecated warning message. Avoid triggering that warning here by using
|
||||
// temporary workaround, __propTypesSecretDontUseThesePlease.
|
||||
// TODO (bvaughn) Revert this particular change any time after April 1
|
||||
const propTypes =
|
||||
Component.__propTypesSecretDontUseThesePlease || Component.propTypes;
|
||||
|
||||
AnimatedComponent.propTypes = {
|
||||
style: function(props, propName, componentName) {
|
||||
if (!propTypes) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const key in ViewStylePropTypes) {
|
||||
if (!propTypes[key] && props[key] !== undefined) {
|
||||
console.warn(
|
||||
'You are setting the style `{ ' +
|
||||
key +
|
||||
': ... }` as a prop. You ' +
|
||||
'should nest it in a style object. ' +
|
||||
'E.g. `{ style: { ' +
|
||||
key +
|
||||
': ... } }`',
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
return AnimatedComponent;
|
||||
}
|
||||
|
||||
module.exports = createAnimatedComponent;
|
||||
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AnimatedAddition
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedInterpolation = require('./AnimatedInterpolation');
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedValue = require('./AnimatedValue');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
|
||||
import type {InterpolationConfigType} from './AnimatedInterpolation';
|
||||
|
||||
class AnimatedAddition extends AnimatedWithChildren {
|
||||
_a: AnimatedNode;
|
||||
_b: AnimatedNode;
|
||||
|
||||
constructor(a: AnimatedNode | number, b: AnimatedNode | number) {
|
||||
super();
|
||||
this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
|
||||
this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
this._a.__makeNative();
|
||||
this._b.__makeNative();
|
||||
super.__makeNative();
|
||||
}
|
||||
|
||||
__getValue(): number {
|
||||
return this._a.__getValue() + this._b.__getValue();
|
||||
}
|
||||
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, config);
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
this._a.__addChild(this);
|
||||
this._b.__addChild(this);
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
this._a.__removeChild(this);
|
||||
this._b.__removeChild(this);
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__getNativeConfig(): any {
|
||||
return {
|
||||
type: 'addition',
|
||||
input: [this._a.__getNativeTag(), this._b.__getNativeTag()],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedAddition;
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AnimatedDiffClamp
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedInterpolation = require('./AnimatedInterpolation');
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
|
||||
import type {InterpolationConfigType} from './AnimatedInterpolation';
|
||||
|
||||
class AnimatedDiffClamp extends AnimatedWithChildren {
|
||||
_a: AnimatedNode;
|
||||
_min: number;
|
||||
_max: number;
|
||||
_value: number;
|
||||
_lastValue: number;
|
||||
|
||||
constructor(a: AnimatedNode, min: number, max: number) {
|
||||
super();
|
||||
|
||||
this._a = a;
|
||||
this._min = min;
|
||||
this._max = max;
|
||||
this._value = this._lastValue = this._a.__getValue();
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
this._a.__makeNative();
|
||||
super.__makeNative();
|
||||
}
|
||||
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, config);
|
||||
}
|
||||
|
||||
__getValue(): number {
|
||||
const value = this._a.__getValue();
|
||||
const diff = value - this._lastValue;
|
||||
this._lastValue = value;
|
||||
this._value = Math.min(Math.max(this._value + diff, this._min), this._max);
|
||||
return this._value;
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
this._a.__addChild(this);
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
this._a.__removeChild(this);
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__getNativeConfig(): any {
|
||||
return {
|
||||
type: 'diffclamp',
|
||||
input: this._a.__getNativeTag(),
|
||||
min: this._min,
|
||||
max: this._max,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedDiffClamp;
|
||||
@@ -1,70 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AnimatedDivision
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedInterpolation = require('./AnimatedInterpolation');
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedValue = require('./AnimatedValue');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
|
||||
import type {InterpolationConfigType} from './AnimatedInterpolation';
|
||||
|
||||
class AnimatedDivision extends AnimatedWithChildren {
|
||||
_a: AnimatedNode;
|
||||
_b: AnimatedNode;
|
||||
|
||||
constructor(a: AnimatedNode | number, b: AnimatedNode | number) {
|
||||
super();
|
||||
this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
|
||||
this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
this._a.__makeNative();
|
||||
this._b.__makeNative();
|
||||
super.__makeNative();
|
||||
}
|
||||
|
||||
__getValue(): number {
|
||||
const a = this._a.__getValue();
|
||||
const b = this._b.__getValue();
|
||||
if (b === 0) {
|
||||
console.error('Detected division by zero in AnimatedDivision');
|
||||
}
|
||||
return a / b;
|
||||
}
|
||||
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, config);
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
this._a.__addChild(this);
|
||||
this._b.__addChild(this);
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
this._a.__removeChild(this);
|
||||
this._b.__removeChild(this);
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__getNativeConfig(): any {
|
||||
return {
|
||||
type: 'division',
|
||||
input: [this._a.__getNativeTag(), this._b.__getNativeTag()],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedDivision;
|
||||
@@ -1,388 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AnimatedInterpolation
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
/* eslint no-bitwise: 0 */
|
||||
'use strict';
|
||||
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const normalizeColor = require('normalizeColor');
|
||||
|
||||
type ExtrapolateType = 'extend' | 'identity' | 'clamp';
|
||||
|
||||
export type InterpolationConfigType = {
|
||||
inputRange: Array<number>,
|
||||
/* $FlowFixMe(>=0.38.0 site=react_native_fb,react_native_oss) - Flow error
|
||||
* detected during the deployment of v0.38.0. To see the error, remove this
|
||||
* comment and run flow
|
||||
*/
|
||||
outputRange: Array<number> | Array<string>,
|
||||
easing?: (input: number) => number,
|
||||
extrapolate?: ExtrapolateType,
|
||||
extrapolateLeft?: ExtrapolateType,
|
||||
extrapolateRight?: ExtrapolateType,
|
||||
};
|
||||
|
||||
const linear = t => t;
|
||||
|
||||
/**
|
||||
* Very handy helper to map input ranges to output ranges with an easing
|
||||
* function and custom behavior outside of the ranges.
|
||||
*/
|
||||
function createInterpolation(
|
||||
config: InterpolationConfigType,
|
||||
): (input: number) => number | string {
|
||||
if (config.outputRange && typeof config.outputRange[0] === 'string') {
|
||||
return createInterpolationFromStringOutputRange(config);
|
||||
}
|
||||
|
||||
const outputRange: Array<number> = (config.outputRange: any);
|
||||
checkInfiniteRange('outputRange', outputRange);
|
||||
|
||||
const inputRange = config.inputRange;
|
||||
checkInfiniteRange('inputRange', inputRange);
|
||||
checkValidInputRange(inputRange);
|
||||
|
||||
invariant(
|
||||
inputRange.length === outputRange.length,
|
||||
'inputRange (' +
|
||||
inputRange.length +
|
||||
') and outputRange (' +
|
||||
outputRange.length +
|
||||
') must have the same length',
|
||||
);
|
||||
|
||||
const easing = config.easing || linear;
|
||||
|
||||
let extrapolateLeft: ExtrapolateType = 'extend';
|
||||
if (config.extrapolateLeft !== undefined) {
|
||||
extrapolateLeft = config.extrapolateLeft;
|
||||
} else if (config.extrapolate !== undefined) {
|
||||
extrapolateLeft = config.extrapolate;
|
||||
}
|
||||
|
||||
let extrapolateRight: ExtrapolateType = 'extend';
|
||||
if (config.extrapolateRight !== undefined) {
|
||||
extrapolateRight = config.extrapolateRight;
|
||||
} else if (config.extrapolate !== undefined) {
|
||||
extrapolateRight = config.extrapolate;
|
||||
}
|
||||
|
||||
return input => {
|
||||
invariant(
|
||||
typeof input === 'number',
|
||||
'Cannot interpolation an input which is not a number',
|
||||
);
|
||||
|
||||
const range = findRange(input, inputRange);
|
||||
return interpolate(
|
||||
input,
|
||||
inputRange[range],
|
||||
inputRange[range + 1],
|
||||
outputRange[range],
|
||||
outputRange[range + 1],
|
||||
easing,
|
||||
extrapolateLeft,
|
||||
extrapolateRight,
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function interpolate(
|
||||
input: number,
|
||||
inputMin: number,
|
||||
inputMax: number,
|
||||
outputMin: number,
|
||||
outputMax: number,
|
||||
easing: (input: number) => number,
|
||||
extrapolateLeft: ExtrapolateType,
|
||||
extrapolateRight: ExtrapolateType,
|
||||
) {
|
||||
let result = input;
|
||||
|
||||
// Extrapolate
|
||||
if (result < inputMin) {
|
||||
if (extrapolateLeft === 'identity') {
|
||||
return result;
|
||||
} else if (extrapolateLeft === 'clamp') {
|
||||
result = inputMin;
|
||||
} else if (extrapolateLeft === 'extend') {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
if (result > inputMax) {
|
||||
if (extrapolateRight === 'identity') {
|
||||
return result;
|
||||
} else if (extrapolateRight === 'clamp') {
|
||||
result = inputMax;
|
||||
} else if (extrapolateRight === 'extend') {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
if (outputMin === outputMax) {
|
||||
return outputMin;
|
||||
}
|
||||
|
||||
if (inputMin === inputMax) {
|
||||
if (input <= inputMin) {
|
||||
return outputMin;
|
||||
}
|
||||
return outputMax;
|
||||
}
|
||||
|
||||
// Input Range
|
||||
if (inputMin === -Infinity) {
|
||||
result = -result;
|
||||
} else if (inputMax === Infinity) {
|
||||
result = result - inputMin;
|
||||
} else {
|
||||
result = (result - inputMin) / (inputMax - inputMin);
|
||||
}
|
||||
|
||||
// Easing
|
||||
result = easing(result);
|
||||
|
||||
// Output Range
|
||||
if (outputMin === -Infinity) {
|
||||
result = -result;
|
||||
} else if (outputMax === Infinity) {
|
||||
result = result + outputMin;
|
||||
} else {
|
||||
result = result * (outputMax - outputMin) + outputMin;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
function colorToRgba(input: string): string {
|
||||
let int32Color = normalizeColor(input);
|
||||
if (int32Color === null) {
|
||||
return input;
|
||||
}
|
||||
|
||||
int32Color = int32Color || 0;
|
||||
|
||||
const r = (int32Color & 0xff000000) >>> 24;
|
||||
const g = (int32Color & 0x00ff0000) >>> 16;
|
||||
const b = (int32Color & 0x0000ff00) >>> 8;
|
||||
const a = (int32Color & 0x000000ff) / 255;
|
||||
|
||||
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
||||
}
|
||||
|
||||
const stringShapeRegex = /[0-9\.-]+/g;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
function createInterpolationFromStringOutputRange(
|
||||
config: InterpolationConfigType,
|
||||
): (input: number) => string {
|
||||
let outputRange: Array<string> = (config.outputRange: any);
|
||||
invariant(outputRange.length >= 2, 'Bad output range');
|
||||
outputRange = outputRange.map(colorToRgba);
|
||||
checkPattern(outputRange);
|
||||
|
||||
// ['rgba(0, 100, 200, 0)', 'rgba(50, 150, 250, 0.5)']
|
||||
// ->
|
||||
// [
|
||||
// [0, 50],
|
||||
// [100, 150],
|
||||
// [200, 250],
|
||||
// [0, 0.5],
|
||||
// ]
|
||||
/* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to
|
||||
* guard against this possibility.
|
||||
*/
|
||||
const outputRanges = outputRange[0].match(stringShapeRegex).map(() => []);
|
||||
outputRange.forEach(value => {
|
||||
/* $FlowFixMe(>=0.18.0): `value.match()` can return `null`. Need to guard
|
||||
* against this possibility.
|
||||
*/
|
||||
value.match(stringShapeRegex).forEach((number, i) => {
|
||||
outputRanges[i].push(+number);
|
||||
});
|
||||
});
|
||||
|
||||
/* $FlowFixMe(>=0.18.0): `outputRange[0].match()` can return `null`. Need to
|
||||
* guard against this possibility.
|
||||
*/
|
||||
const interpolations = outputRange[0]
|
||||
.match(stringShapeRegex)
|
||||
.map((value, i) => {
|
||||
return createInterpolation({
|
||||
...config,
|
||||
outputRange: outputRanges[i],
|
||||
});
|
||||
});
|
||||
|
||||
// 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).
|
||||
const shouldRound = isRgbOrRgba(outputRange[0]);
|
||||
|
||||
return input => {
|
||||
let i = 0;
|
||||
// 'rgba(0, 100, 200, 0)'
|
||||
// ->
|
||||
// 'rgba(${interpolations[0](input)}, ${interpolations[1](input)}, ...'
|
||||
return outputRange[0].replace(stringShapeRegex, () => {
|
||||
const val = +interpolations[i++](input);
|
||||
const rounded =
|
||||
shouldRound && i < 4 ? Math.round(val) : Math.round(val * 1000) / 1000;
|
||||
return String(rounded);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function isRgbOrRgba(range) {
|
||||
return typeof range === 'string' && range.startsWith('rgb');
|
||||
}
|
||||
|
||||
function checkPattern(arr: Array<string>) {
|
||||
const pattern = arr[0].replace(stringShapeRegex, '');
|
||||
for (let i = 1; i < arr.length; ++i) {
|
||||
invariant(
|
||||
pattern === arr[i].replace(stringShapeRegex, ''),
|
||||
'invalid pattern ' + arr[0] + ' and ' + arr[i],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function findRange(input: number, inputRange: Array<number>) {
|
||||
let i;
|
||||
for (i = 1; i < inputRange.length - 1; ++i) {
|
||||
if (inputRange[i] >= input) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i - 1;
|
||||
}
|
||||
|
||||
function checkValidInputRange(arr: Array<number>) {
|
||||
invariant(arr.length >= 2, 'inputRange must have at least 2 elements');
|
||||
for (let i = 1; i < arr.length; ++i) {
|
||||
invariant(
|
||||
arr[i] >= arr[i - 1],
|
||||
/* $FlowFixMe(>=0.13.0) - In the addition expression below this comment,
|
||||
* one or both of the operands may be something that doesn't cleanly
|
||||
* convert to a string, like undefined, null, and object, etc. If you really
|
||||
* mean this implicit string conversion, you can do something like
|
||||
* String(myThing)
|
||||
*/
|
||||
'inputRange must be monotonically increasing ' + arr,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function checkInfiniteRange(name: string, arr: Array<number>) {
|
||||
invariant(arr.length >= 2, name + ' must have at least 2 elements');
|
||||
invariant(
|
||||
arr.length !== 2 || arr[0] !== -Infinity || arr[1] !== Infinity,
|
||||
/* $FlowFixMe(>=0.13.0) - In the addition expression below this comment,
|
||||
* one or both of the operands may be something that doesn't cleanly convert
|
||||
* to a string, like undefined, null, and object, etc. If you really mean
|
||||
* this implicit string conversion, you can do something like
|
||||
* String(myThing)
|
||||
*/
|
||||
name + 'cannot be ]-infinity;+infinity[ ' + arr,
|
||||
);
|
||||
}
|
||||
|
||||
class AnimatedInterpolation extends AnimatedWithChildren {
|
||||
// Export for testing.
|
||||
static __createInterpolation = createInterpolation;
|
||||
|
||||
_parent: AnimatedNode;
|
||||
_config: InterpolationConfigType;
|
||||
_interpolation: (input: number) => number | string;
|
||||
|
||||
constructor(parent: AnimatedNode, config: InterpolationConfigType) {
|
||||
super();
|
||||
this._parent = parent;
|
||||
this._config = config;
|
||||
this._interpolation = createInterpolation(config);
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
this._parent.__makeNative();
|
||||
super.__makeNative();
|
||||
}
|
||||
|
||||
__getValue(): number | string {
|
||||
const parentValue: number = this._parent.__getValue();
|
||||
invariant(
|
||||
typeof parentValue === 'number',
|
||||
'Cannot interpolate an input which is not a number.',
|
||||
);
|
||||
return this._interpolation(parentValue);
|
||||
}
|
||||
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, config);
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
this._parent.__addChild(this);
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
this._parent.__removeChild(this);
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__transformDataType(range: Array<any>) {
|
||||
// Change the string array type to number array
|
||||
// So we can reuse the same logic in iOS and Android platform
|
||||
return range.map(function(value) {
|
||||
if (typeof value !== 'string') {
|
||||
return value;
|
||||
}
|
||||
if (/deg$/.test(value)) {
|
||||
const degrees = parseFloat(value) || 0;
|
||||
const radians = degrees * Math.PI / 180.0;
|
||||
return radians;
|
||||
} else {
|
||||
// Assume radians
|
||||
return parseFloat(value) || 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
__getNativeConfig(): any {
|
||||
if (__DEV__) {
|
||||
NativeAnimatedHelper.validateInterpolation(this._config);
|
||||
}
|
||||
|
||||
return {
|
||||
inputRange: this._config.inputRange,
|
||||
// Only the `outputRange` can contain strings so we don't need to tranform `inputRange` here
|
||||
outputRange: this.__transformDataType(this._config.outputRange),
|
||||
extrapolateLeft:
|
||||
this._config.extrapolateLeft || this._config.extrapolate || 'extend',
|
||||
extrapolateRight:
|
||||
this._config.extrapolateRight || this._config.extrapolate || 'extend',
|
||||
type: 'interpolation',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedInterpolation;
|
||||
@@ -1,64 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AnimatedModulo
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedInterpolation = require('./AnimatedInterpolation');
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
|
||||
import type {InterpolationConfigType} from './AnimatedInterpolation';
|
||||
|
||||
class AnimatedModulo extends AnimatedWithChildren {
|
||||
_a: AnimatedNode;
|
||||
_modulus: number;
|
||||
|
||||
constructor(a: AnimatedNode, modulus: number) {
|
||||
super();
|
||||
this._a = a;
|
||||
this._modulus = modulus;
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
this._a.__makeNative();
|
||||
super.__makeNative();
|
||||
}
|
||||
|
||||
__getValue(): number {
|
||||
return (
|
||||
(this._a.__getValue() % this._modulus + this._modulus) % this._modulus
|
||||
);
|
||||
}
|
||||
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, config);
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
this._a.__addChild(this);
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
this._a.__removeChild(this);
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__getNativeConfig(): any {
|
||||
return {
|
||||
type: 'modulus',
|
||||
input: this._a.__getNativeTag(),
|
||||
modulus: this._modulus,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedModulo;
|
||||
@@ -1,65 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AnimatedMultiplication
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedInterpolation = require('./AnimatedInterpolation');
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedValue = require('./AnimatedValue');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
|
||||
import type {InterpolationConfigType} from './AnimatedInterpolation';
|
||||
|
||||
class AnimatedMultiplication extends AnimatedWithChildren {
|
||||
_a: AnimatedNode;
|
||||
_b: AnimatedNode;
|
||||
|
||||
constructor(a: AnimatedNode | number, b: AnimatedNode | number) {
|
||||
super();
|
||||
this._a = typeof a === 'number' ? new AnimatedValue(a) : a;
|
||||
this._b = typeof b === 'number' ? new AnimatedValue(b) : b;
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
this._a.__makeNative();
|
||||
this._b.__makeNative();
|
||||
super.__makeNative();
|
||||
}
|
||||
|
||||
__getValue(): number {
|
||||
return this._a.__getValue() * this._b.__getValue();
|
||||
}
|
||||
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, config);
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
this._a.__addChild(this);
|
||||
this._b.__addChild(this);
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
this._a.__removeChild(this);
|
||||
this._b.__removeChild(this);
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__getNativeConfig(): any {
|
||||
return {
|
||||
type: 'multiplication',
|
||||
input: [this._a.__getNativeTag(), this._b.__getNativeTag()],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedMultiplication;
|
||||
@@ -1,73 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AnimatedNode
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
// Note(vjeux): this would be better as an interface but flow doesn't
|
||||
// support them yet
|
||||
class AnimatedNode {
|
||||
__attach(): void {}
|
||||
__detach(): void {
|
||||
if (this.__isNative && this.__nativeTag != null) {
|
||||
NativeAnimatedHelper.API.dropAnimatedNode(this.__nativeTag);
|
||||
this.__nativeTag = undefined;
|
||||
}
|
||||
}
|
||||
__getValue(): any {}
|
||||
__getAnimatedValue(): any {
|
||||
return this.__getValue();
|
||||
}
|
||||
__addChild(child: AnimatedNode) {}
|
||||
__removeChild(child: AnimatedNode) {}
|
||||
__getChildren(): Array<AnimatedNode> {
|
||||
return [];
|
||||
}
|
||||
|
||||
/* Methods and props used by native Animated impl */
|
||||
__isNative: boolean;
|
||||
__nativeTag: ?number;
|
||||
__makeNative() {
|
||||
if (!this.__isNative) {
|
||||
throw new Error('This node cannot be made a "native" animated node');
|
||||
}
|
||||
}
|
||||
__getNativeTag(): ?number {
|
||||
NativeAnimatedHelper.assertNativeAnimatedModule();
|
||||
invariant(
|
||||
this.__isNative,
|
||||
'Attempt to get native tag from node not marked as "native"',
|
||||
);
|
||||
if (this.__nativeTag == null) {
|
||||
const nativeTag: ?number = NativeAnimatedHelper.generateNewNodeTag();
|
||||
NativeAnimatedHelper.API.createAnimatedNode(
|
||||
nativeTag,
|
||||
this.__getNativeConfig(),
|
||||
);
|
||||
this.__nativeTag = nativeTag;
|
||||
}
|
||||
return this.__nativeTag;
|
||||
}
|
||||
__getNativeConfig(): Object {
|
||||
throw new Error(
|
||||
'This JS animated node type cannot be used as native animated node',
|
||||
);
|
||||
}
|
||||
toJSON(): any {
|
||||
return this.__getValue();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedNode;
|
||||
@@ -1,167 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AnimatedProps
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const {AnimatedEvent} = require('../AnimatedEvent');
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedStyle = require('./AnimatedStyle');
|
||||
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
|
||||
const ReactNative = require('ReactNative');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
class AnimatedProps extends AnimatedNode {
|
||||
_props: Object;
|
||||
_animatedView: any;
|
||||
_callback: () => void;
|
||||
|
||||
constructor(props: Object, callback: () => void) {
|
||||
super();
|
||||
if (props.style) {
|
||||
props = {
|
||||
...props,
|
||||
style: new AnimatedStyle(props.style),
|
||||
};
|
||||
}
|
||||
this._props = props;
|
||||
this._callback = callback;
|
||||
this.__attach();
|
||||
}
|
||||
|
||||
__getValue(): Object {
|
||||
const props = {};
|
||||
for (const key in this._props) {
|
||||
const value = this._props[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
if (!value.__isNative || value instanceof AnimatedStyle) {
|
||||
// We cannot use value of natively driven nodes this way as the value we have access from
|
||||
// JS may not be up to date.
|
||||
props[key] = value.__getValue();
|
||||
}
|
||||
} else if (value instanceof AnimatedEvent) {
|
||||
props[key] = value.__getHandler();
|
||||
} else {
|
||||
props[key] = value;
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
__getAnimatedValue(): Object {
|
||||
const props = {};
|
||||
for (const key in this._props) {
|
||||
const value = this._props[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
props[key] = value.__getAnimatedValue();
|
||||
}
|
||||
}
|
||||
return props;
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
for (const key in this._props) {
|
||||
const value = this._props[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__addChild(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
if (this.__isNative && this._animatedView) {
|
||||
this.__disconnectAnimatedView();
|
||||
}
|
||||
for (const key in this._props) {
|
||||
const value = this._props[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__removeChild(this);
|
||||
}
|
||||
}
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
update(): void {
|
||||
this._callback();
|
||||
}
|
||||
|
||||
__makeNative(): void {
|
||||
if (!this.__isNative) {
|
||||
this.__isNative = true;
|
||||
for (const key in this._props) {
|
||||
const value = this._props[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__makeNative();
|
||||
}
|
||||
}
|
||||
if (this._animatedView) {
|
||||
this.__connectAnimatedView();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setNativeView(animatedView: any): void {
|
||||
if (this._animatedView === animatedView) {
|
||||
return;
|
||||
}
|
||||
this._animatedView = animatedView;
|
||||
if (this.__isNative) {
|
||||
this.__connectAnimatedView();
|
||||
}
|
||||
}
|
||||
|
||||
__connectAnimatedView(): void {
|
||||
invariant(this.__isNative, 'Expected node to be marked as "native"');
|
||||
const nativeViewTag: ?number = ReactNative.findNodeHandle(
|
||||
this._animatedView,
|
||||
);
|
||||
invariant(
|
||||
nativeViewTag != null,
|
||||
'Unable to locate attached view in the native tree',
|
||||
);
|
||||
NativeAnimatedHelper.API.connectAnimatedNodeToView(
|
||||
this.__getNativeTag(),
|
||||
nativeViewTag,
|
||||
);
|
||||
}
|
||||
|
||||
__disconnectAnimatedView(): void {
|
||||
invariant(this.__isNative, 'Expected node to be marked as "native"');
|
||||
const nativeViewTag: ?number = ReactNative.findNodeHandle(
|
||||
this._animatedView,
|
||||
);
|
||||
invariant(
|
||||
nativeViewTag != null,
|
||||
'Unable to locate attached view in the native tree',
|
||||
);
|
||||
NativeAnimatedHelper.API.disconnectAnimatedNodeFromView(
|
||||
this.__getNativeTag(),
|
||||
nativeViewTag,
|
||||
);
|
||||
}
|
||||
|
||||
__getNativeConfig(): Object {
|
||||
const propsConfig = {};
|
||||
for (const propKey in this._props) {
|
||||
const value = this._props[propKey];
|
||||
if (value instanceof AnimatedNode) {
|
||||
propsConfig[propKey] = value.__getNativeTag();
|
||||
}
|
||||
}
|
||||
return {
|
||||
type: 'props',
|
||||
props: propsConfig,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedProps;
|
||||
@@ -1,127 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AnimatedStyle
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedTransform = require('./AnimatedTransform');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
|
||||
|
||||
const flattenStyle = require('flattenStyle');
|
||||
|
||||
class AnimatedStyle extends AnimatedWithChildren {
|
||||
_style: Object;
|
||||
|
||||
constructor(style: any) {
|
||||
super();
|
||||
style = flattenStyle(style) || {};
|
||||
if (style.transform) {
|
||||
style = {
|
||||
...style,
|
||||
transform: new AnimatedTransform(style.transform),
|
||||
};
|
||||
}
|
||||
this._style = style;
|
||||
}
|
||||
|
||||
// Recursively get values for nested styles (like iOS's shadowOffset)
|
||||
_walkStyleAndGetValues(style) {
|
||||
const updatedStyle = {};
|
||||
for (const key in style) {
|
||||
const value = style[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
if (!value.__isNative) {
|
||||
// We cannot use value of natively driven nodes this way as the value we have access from
|
||||
// JS may not be up to date.
|
||||
updatedStyle[key] = value.__getValue();
|
||||
}
|
||||
} else if (value && !Array.isArray(value) && typeof value === 'object') {
|
||||
// Support animating nested values (for example: shadowOffset.height)
|
||||
updatedStyle[key] = this._walkStyleAndGetValues(value);
|
||||
} else {
|
||||
updatedStyle[key] = value;
|
||||
}
|
||||
}
|
||||
return updatedStyle;
|
||||
}
|
||||
|
||||
__getValue(): Object {
|
||||
return this._walkStyleAndGetValues(this._style);
|
||||
}
|
||||
|
||||
// Recursively get animated values for nested styles (like iOS's shadowOffset)
|
||||
_walkStyleAndGetAnimatedValues(style) {
|
||||
const updatedStyle = {};
|
||||
for (const key in style) {
|
||||
const value = style[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
updatedStyle[key] = value.__getAnimatedValue();
|
||||
} else if (value && !Array.isArray(value) && typeof value === 'object') {
|
||||
// Support animating nested values (for example: shadowOffset.height)
|
||||
updatedStyle[key] = this._walkStyleAndGetAnimatedValues(value);
|
||||
}
|
||||
}
|
||||
return updatedStyle;
|
||||
}
|
||||
|
||||
__getAnimatedValue(): Object {
|
||||
return this._walkStyleAndGetAnimatedValues(this._style);
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
for (const key in this._style) {
|
||||
const value = this._style[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__addChild(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
for (const key in this._style) {
|
||||
const value = this._style[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__removeChild(this);
|
||||
}
|
||||
}
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
super.__makeNative();
|
||||
for (const key in this._style) {
|
||||
const value = this._style[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__makeNative();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__getNativeConfig(): Object {
|
||||
const styleConfig = {};
|
||||
for (const styleKey in this._style) {
|
||||
if (this._style[styleKey] instanceof AnimatedNode) {
|
||||
styleConfig[styleKey] = this._style[styleKey].__getNativeTag();
|
||||
}
|
||||
// Non-animated styles are set using `setNativeProps`, no need
|
||||
// to pass those as a part of the node config
|
||||
}
|
||||
NativeAnimatedHelper.validateStyles(styleConfig);
|
||||
return {
|
||||
type: 'style',
|
||||
style: styleConfig,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedStyle;
|
||||
@@ -1,67 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AnimatedTracking
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedValue = require('./AnimatedValue');
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
|
||||
import type {EndCallback} from '../animations/Animation';
|
||||
|
||||
class AnimatedTracking extends AnimatedNode {
|
||||
_value: AnimatedValue;
|
||||
_parent: AnimatedNode;
|
||||
_callback: ?EndCallback;
|
||||
_animationConfig: Object;
|
||||
_animationClass: any;
|
||||
|
||||
constructor(
|
||||
value: AnimatedValue,
|
||||
parent: AnimatedNode,
|
||||
animationClass: any,
|
||||
animationConfig: Object,
|
||||
callback?: ?EndCallback,
|
||||
) {
|
||||
super();
|
||||
this._value = value;
|
||||
this._parent = parent;
|
||||
this._animationClass = animationClass;
|
||||
this._animationConfig = animationConfig;
|
||||
this._callback = callback;
|
||||
this.__attach();
|
||||
}
|
||||
|
||||
__getValue(): Object {
|
||||
return this._parent.__getValue();
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
this._parent.__addChild(this);
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
this._parent.__removeChild(this);
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
update(): void {
|
||||
this._value.animate(
|
||||
new this._animationClass({
|
||||
...this._animationConfig,
|
||||
toValue: (this._animationConfig.toValue: any).__getValue(),
|
||||
}),
|
||||
this._callback,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedTracking;
|
||||
@@ -1,123 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AnimatedTransform
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
|
||||
|
||||
class AnimatedTransform extends AnimatedWithChildren {
|
||||
_transforms: Array<Object>;
|
||||
|
||||
constructor(transforms: Array<Object>) {
|
||||
super();
|
||||
this._transforms = transforms;
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
super.__makeNative();
|
||||
this._transforms.forEach(transform => {
|
||||
for (const key in transform) {
|
||||
const value = transform[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__makeNative();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
__getValue(): Array<Object> {
|
||||
return this._transforms.map(transform => {
|
||||
const result = {};
|
||||
for (const key in transform) {
|
||||
const value = transform[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
result[key] = value.__getValue();
|
||||
} else {
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
__getAnimatedValue(): Array<Object> {
|
||||
return this._transforms.map(transform => {
|
||||
const result = {};
|
||||
for (const key in transform) {
|
||||
const value = transform[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
result[key] = value.__getAnimatedValue();
|
||||
} else {
|
||||
// All transform components needed to recompose matrix
|
||||
result[key] = value;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
__attach(): void {
|
||||
this._transforms.forEach(transform => {
|
||||
for (const key in transform) {
|
||||
const value = transform[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__addChild(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
__detach(): void {
|
||||
this._transforms.forEach(transform => {
|
||||
for (const key in transform) {
|
||||
const value = transform[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
value.__removeChild(this);
|
||||
}
|
||||
}
|
||||
});
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__getNativeConfig(): any {
|
||||
const transConfigs = [];
|
||||
|
||||
this._transforms.forEach(transform => {
|
||||
for (const key in transform) {
|
||||
const value = transform[key];
|
||||
if (value instanceof AnimatedNode) {
|
||||
transConfigs.push({
|
||||
type: 'animated',
|
||||
property: key,
|
||||
nodeTag: value.__getNativeTag(),
|
||||
});
|
||||
} else {
|
||||
transConfigs.push({
|
||||
type: 'static',
|
||||
property: key,
|
||||
value,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
NativeAnimatedHelper.validateTransform(transConfigs);
|
||||
return {
|
||||
type: 'transform',
|
||||
transforms: transConfigs,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedTransform;
|
||||
@@ -1,309 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AnimatedValue
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedInterpolation = require('./AnimatedInterpolation');
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
const InteractionManager = require('InteractionManager');
|
||||
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
|
||||
|
||||
import type Animation, {EndCallback} from '../animations/Animation';
|
||||
import type {InterpolationConfigType} from './AnimatedInterpolation';
|
||||
|
||||
const NativeAnimatedAPI = NativeAnimatedHelper.API;
|
||||
|
||||
type ValueListenerCallback = (state: {value: number}) => void;
|
||||
|
||||
let _uniqueId = 1;
|
||||
|
||||
/**
|
||||
* Animated works by building a directed acyclic graph of dependencies
|
||||
* transparently when you render your Animated components.
|
||||
*
|
||||
* new Animated.Value(0)
|
||||
* .interpolate() .interpolate() new Animated.Value(1)
|
||||
* opacity translateY scale
|
||||
* style transform
|
||||
* View#234 style
|
||||
* View#123
|
||||
*
|
||||
* A) Top Down phase
|
||||
* When an Animated.Value is updated, we recursively go down through this
|
||||
* graph in order to find leaf nodes: the views that we flag as needing
|
||||
* an update.
|
||||
*
|
||||
* B) Bottom Up phase
|
||||
* When a view is flagged as needing an update, we recursively go back up
|
||||
* in order to build the new value that it needs. The reason why we need
|
||||
* this two-phases process is to deal with composite props such as
|
||||
* transform which can receive values from multiple parents.
|
||||
*/
|
||||
function _flush(rootNode: AnimatedValue): void {
|
||||
const animatedStyles = new Set();
|
||||
function findAnimatedStyles(node) {
|
||||
if (typeof node.update === 'function') {
|
||||
animatedStyles.add(node);
|
||||
} else {
|
||||
node.__getChildren().forEach(findAnimatedStyles);
|
||||
}
|
||||
}
|
||||
findAnimatedStyles(rootNode);
|
||||
/* $FlowFixMe */
|
||||
animatedStyles.forEach(animatedStyle => animatedStyle.update());
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard value for driving animations. One `Animated.Value` can drive
|
||||
* multiple properties in a synchronized fashion, but can only be driven by one
|
||||
* mechanism at a time. Using a new mechanism (e.g. starting a new animation,
|
||||
* or calling `setValue`) will stop any previous ones.
|
||||
*/
|
||||
class AnimatedValue extends AnimatedWithChildren {
|
||||
_value: number;
|
||||
_startingValue: number;
|
||||
_offset: number;
|
||||
_animation: ?Animation;
|
||||
_tracking: ?AnimatedNode;
|
||||
_listeners: {[key: string]: ValueListenerCallback};
|
||||
__nativeAnimatedValueListener: ?any;
|
||||
|
||||
constructor(value: number) {
|
||||
super();
|
||||
this._startingValue = this._value = value;
|
||||
this._offset = 0;
|
||||
this._animation = null;
|
||||
this._listeners = {};
|
||||
}
|
||||
|
||||
__detach() {
|
||||
this.stopAnimation();
|
||||
super.__detach();
|
||||
}
|
||||
|
||||
__getValue(): number {
|
||||
return this._value + this._offset;
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
super.__makeNative();
|
||||
|
||||
if (Object.keys(this._listeners).length) {
|
||||
this._startListeningToNativeValueUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly set the value. This will stop any animations running on the value
|
||||
* and update all the bound properties.
|
||||
*/
|
||||
setValue(value: number): void {
|
||||
if (this._animation) {
|
||||
this._animation.stop();
|
||||
this._animation = null;
|
||||
}
|
||||
this._updateValue(
|
||||
value,
|
||||
!this.__isNative /* don't perform a flush for natively driven values */,
|
||||
);
|
||||
if (this.__isNative) {
|
||||
NativeAnimatedAPI.setAnimatedNodeValue(this.__getNativeTag(), value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an offset that is applied on top of whatever value is set, whether via
|
||||
* `setValue`, an animation, or `Animated.event`. Useful for compensating
|
||||
* things like the start of a pan gesture.
|
||||
*/
|
||||
setOffset(offset: number): void {
|
||||
this._offset = offset;
|
||||
if (this.__isNative) {
|
||||
NativeAnimatedAPI.setAnimatedNodeOffset(this.__getNativeTag(), offset);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the offset value into the base value and resets the offset to zero.
|
||||
* The final output of the value is unchanged.
|
||||
*/
|
||||
flattenOffset(): void {
|
||||
this._value += this._offset;
|
||||
this._offset = 0;
|
||||
if (this.__isNative) {
|
||||
NativeAnimatedAPI.flattenAnimatedNodeOffset(this.__getNativeTag());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the offset value to the base value, and resets the base value to zero.
|
||||
* The final output of the value is unchanged.
|
||||
*/
|
||||
extractOffset(): void {
|
||||
this._offset += this._value;
|
||||
this._value = 0;
|
||||
if (this.__isNative) {
|
||||
NativeAnimatedAPI.extractAnimatedNodeOffset(this.__getNativeTag());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an asynchronous listener to the value so you can observe updates from
|
||||
* animations. This is useful because there is no way to
|
||||
* synchronously read the value because it might be driven natively.
|
||||
*/
|
||||
addListener(callback: ValueListenerCallback): string {
|
||||
const id = String(_uniqueId++);
|
||||
this._listeners[id] = callback;
|
||||
if (this.__isNative) {
|
||||
this._startListeningToNativeValueUpdates();
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
removeListener(id: string): void {
|
||||
delete this._listeners[id];
|
||||
if (this.__isNative && Object.keys(this._listeners).length === 0) {
|
||||
this._stopListeningForNativeValueUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
removeAllListeners(): void {
|
||||
this._listeners = {};
|
||||
if (this.__isNative) {
|
||||
this._stopListeningForNativeValueUpdates();
|
||||
}
|
||||
}
|
||||
|
||||
_startListeningToNativeValueUpdates() {
|
||||
if (this.__nativeAnimatedValueListener) {
|
||||
return;
|
||||
}
|
||||
|
||||
NativeAnimatedAPI.startListeningToAnimatedNodeValue(this.__getNativeTag());
|
||||
this.__nativeAnimatedValueListener = NativeAnimatedHelper.nativeEventEmitter.addListener(
|
||||
'onAnimatedValueUpdate',
|
||||
data => {
|
||||
if (data.tag !== this.__getNativeTag()) {
|
||||
return;
|
||||
}
|
||||
this._updateValue(data.value, false /* flush */);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
_stopListeningForNativeValueUpdates() {
|
||||
if (!this.__nativeAnimatedValueListener) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.__nativeAnimatedValueListener.remove();
|
||||
this.__nativeAnimatedValueListener = null;
|
||||
NativeAnimatedAPI.stopListeningToAnimatedNodeValue(this.__getNativeTag());
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops any running animation or tracking. `callback` is invoked with the
|
||||
* final value after stopping the animation, which is useful for updating
|
||||
* state to match the animation position with layout.
|
||||
*/
|
||||
stopAnimation(callback?: ?(value: number) => void): void {
|
||||
this.stopTracking();
|
||||
this._animation && this._animation.stop();
|
||||
this._animation = null;
|
||||
callback && callback(this.__getValue());
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops any animation and resets the value to its original
|
||||
*/
|
||||
resetAnimation(callback?: ?(value: number) => void): void {
|
||||
this.stopAnimation(callback);
|
||||
this._value = this._startingValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates the value before updating the property, e.g. mapping 0-1 to
|
||||
* 0-10.
|
||||
*/
|
||||
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
|
||||
return new AnimatedInterpolation(this, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Typically only used internally, but could be used by a custom Animation
|
||||
* class.
|
||||
*/
|
||||
animate(animation: Animation, callback: ?EndCallback): void {
|
||||
let handle = null;
|
||||
if (animation.__isInteraction) {
|
||||
handle = InteractionManager.createInteractionHandle();
|
||||
}
|
||||
const previousAnimation = this._animation;
|
||||
this._animation && this._animation.stop();
|
||||
this._animation = animation;
|
||||
animation.start(
|
||||
this._value,
|
||||
value => {
|
||||
// Natively driven animations will never call into that callback, therefore we can always
|
||||
// pass flush = true to allow the updated value to propagate to native with setNativeProps
|
||||
this._updateValue(value, true /* flush */);
|
||||
},
|
||||
result => {
|
||||
this._animation = null;
|
||||
if (handle !== null) {
|
||||
InteractionManager.clearInteractionHandle(handle);
|
||||
}
|
||||
callback && callback(result);
|
||||
},
|
||||
previousAnimation,
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Typically only used internally.
|
||||
*/
|
||||
stopTracking(): void {
|
||||
this._tracking && this._tracking.__detach();
|
||||
this._tracking = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Typically only used internally.
|
||||
*/
|
||||
track(tracking: AnimatedNode): void {
|
||||
this.stopTracking();
|
||||
this._tracking = tracking;
|
||||
}
|
||||
|
||||
_updateValue(value: number, flush: boolean): void {
|
||||
this._value = value;
|
||||
if (flush) {
|
||||
_flush(this);
|
||||
}
|
||||
for (const key in this._listeners) {
|
||||
this._listeners[key]({value: this.__getValue()});
|
||||
}
|
||||
}
|
||||
|
||||
__getNativeConfig(): Object {
|
||||
return {
|
||||
type: 'value',
|
||||
value: this._value,
|
||||
offset: this._offset,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedValue;
|
||||
@@ -1,180 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AnimatedValueXY
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedValue = require('./AnimatedValue');
|
||||
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
type ValueXYListenerCallback = (value: {x: number, y: number}) => void;
|
||||
|
||||
let _uniqueId = 1;
|
||||
|
||||
/**
|
||||
* 2D Value for driving 2D animations, such as pan gestures. Almost identical
|
||||
* API to normal `Animated.Value`, but multiplexed. Contains two regular
|
||||
* `Animated.Value`s under the hood.
|
||||
*
|
||||
* #### Example
|
||||
*
|
||||
*```javascript
|
||||
* class DraggableView extends React.Component {
|
||||
* constructor(props) {
|
||||
* super(props);
|
||||
* this.state = {
|
||||
* pan: new Animated.ValueXY(), // inits to zero
|
||||
* };
|
||||
* this.state.panResponder = PanResponder.create({
|
||||
* onStartShouldSetPanResponder: () => true,
|
||||
* onPanResponderMove: Animated.event([null, {
|
||||
* dx: this.state.pan.x, // x,y are Animated.Value
|
||||
* dy: this.state.pan.y,
|
||||
* }]),
|
||||
* onPanResponderRelease: () => {
|
||||
* Animated.spring(
|
||||
* this.state.pan, // Auto-multiplexed
|
||||
* {toValue: {x: 0, y: 0}} // Back to zero
|
||||
* ).start();
|
||||
* },
|
||||
* });
|
||||
* }
|
||||
* render() {
|
||||
* return (
|
||||
* <Animated.View
|
||||
* {...this.state.panResponder.panHandlers}
|
||||
* style={this.state.pan.getLayout()}>
|
||||
* {this.props.children}
|
||||
* </Animated.View>
|
||||
* );
|
||||
* }
|
||||
* }
|
||||
*```
|
||||
*/
|
||||
class AnimatedValueXY extends AnimatedWithChildren {
|
||||
x: AnimatedValue;
|
||||
y: AnimatedValue;
|
||||
_listeners: {[key: string]: {x: string, y: string}};
|
||||
|
||||
constructor(
|
||||
valueIn?: ?{x: number | AnimatedValue, y: number | AnimatedValue},
|
||||
) {
|
||||
super();
|
||||
const value: any = valueIn || {x: 0, y: 0}; // @flowfixme: shouldn't need `: any`
|
||||
if (typeof value.x === 'number' && typeof value.y === 'number') {
|
||||
this.x = new AnimatedValue(value.x);
|
||||
this.y = new AnimatedValue(value.y);
|
||||
} else {
|
||||
invariant(
|
||||
value.x instanceof AnimatedValue && value.y instanceof AnimatedValue,
|
||||
'AnimatedValueXY must be initalized with an object of numbers or ' +
|
||||
'AnimatedValues.',
|
||||
);
|
||||
this.x = value.x;
|
||||
this.y = value.y;
|
||||
}
|
||||
this._listeners = {};
|
||||
}
|
||||
|
||||
setValue(value: {x: number, y: number}) {
|
||||
this.x.setValue(value.x);
|
||||
this.y.setValue(value.y);
|
||||
}
|
||||
|
||||
setOffset(offset: {x: number, y: number}) {
|
||||
this.x.setOffset(offset.x);
|
||||
this.y.setOffset(offset.y);
|
||||
}
|
||||
|
||||
flattenOffset(): void {
|
||||
this.x.flattenOffset();
|
||||
this.y.flattenOffset();
|
||||
}
|
||||
|
||||
extractOffset(): void {
|
||||
this.x.extractOffset();
|
||||
this.y.extractOffset();
|
||||
}
|
||||
|
||||
__getValue(): {x: number, y: number} {
|
||||
return {
|
||||
x: this.x.__getValue(),
|
||||
y: this.y.__getValue(),
|
||||
};
|
||||
}
|
||||
|
||||
resetAnimation(callback?: (value: {x: number, y: number}) => void): void {
|
||||
this.x.resetAnimation();
|
||||
this.y.resetAnimation();
|
||||
callback && callback(this.__getValue());
|
||||
}
|
||||
|
||||
stopAnimation(callback?: (value: {x: number, y: number}) => void): void {
|
||||
this.x.stopAnimation();
|
||||
this.y.stopAnimation();
|
||||
callback && callback(this.__getValue());
|
||||
}
|
||||
|
||||
addListener(callback: ValueXYListenerCallback): string {
|
||||
const id = String(_uniqueId++);
|
||||
const jointCallback = ({value: number}) => {
|
||||
callback(this.__getValue());
|
||||
};
|
||||
this._listeners[id] = {
|
||||
x: this.x.addListener(jointCallback),
|
||||
y: this.y.addListener(jointCallback),
|
||||
};
|
||||
return id;
|
||||
}
|
||||
|
||||
removeListener(id: string): void {
|
||||
this.x.removeListener(this._listeners[id].x);
|
||||
this.y.removeListener(this._listeners[id].y);
|
||||
delete this._listeners[id];
|
||||
}
|
||||
|
||||
removeAllListeners(): void {
|
||||
this.x.removeAllListeners();
|
||||
this.y.removeAllListeners();
|
||||
this._listeners = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts `{x, y}` into `{left, top}` for use in style, e.g.
|
||||
*
|
||||
*```javascript
|
||||
* style={this.state.anim.getLayout()}
|
||||
*```
|
||||
*/
|
||||
getLayout(): {[key: string]: AnimatedValue} {
|
||||
return {
|
||||
left: this.x,
|
||||
top: this.y,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts `{x, y}` into a useable translation transform, e.g.
|
||||
*
|
||||
*```javascript
|
||||
* style={{
|
||||
* transform: this.state.anim.getTranslateTransform()
|
||||
* }}
|
||||
*```
|
||||
*/
|
||||
getTranslateTransform(): Array<{[key: string]: AnimatedValue}> {
|
||||
return [{translateX: this.x}, {translateY: this.y}];
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedValueXY;
|
||||
@@ -1,77 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AnimatedWithChildren
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const AnimatedNode = require('./AnimatedNode');
|
||||
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
|
||||
|
||||
class AnimatedWithChildren extends AnimatedNode {
|
||||
_children: Array<AnimatedNode>;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._children = [];
|
||||
}
|
||||
|
||||
__makeNative() {
|
||||
if (!this.__isNative) {
|
||||
this.__isNative = true;
|
||||
for (const child of this._children) {
|
||||
child.__makeNative();
|
||||
NativeAnimatedHelper.API.connectAnimatedNodes(
|
||||
this.__getNativeTag(),
|
||||
child.__getNativeTag(),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
__addChild(child: AnimatedNode): void {
|
||||
if (this._children.length === 0) {
|
||||
this.__attach();
|
||||
}
|
||||
this._children.push(child);
|
||||
if (this.__isNative) {
|
||||
// Only accept "native" animated nodes as children
|
||||
child.__makeNative();
|
||||
NativeAnimatedHelper.API.connectAnimatedNodes(
|
||||
this.__getNativeTag(),
|
||||
child.__getNativeTag(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
__removeChild(child: AnimatedNode): void {
|
||||
const index = this._children.indexOf(child);
|
||||
if (index === -1) {
|
||||
console.warn("Trying to remove a child that doesn't exist");
|
||||
return;
|
||||
}
|
||||
if (this.__isNative && child.__isNative) {
|
||||
NativeAnimatedHelper.API.disconnectAnimatedNodes(
|
||||
this.__getNativeTag(),
|
||||
child.__getNativeTag(),
|
||||
);
|
||||
}
|
||||
this._children.splice(index, 1);
|
||||
if (this._children.length === 0) {
|
||||
this.__detach();
|
||||
}
|
||||
}
|
||||
|
||||
__getChildren(): Array<AnimatedNode> {
|
||||
return this._children;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AnimatedWithChildren;
|
||||
@@ -1,15 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
createInteractionHandle: function() {},
|
||||
clearInteractionHandle: function() {}
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
function SetPolyfill() {
|
||||
this._cache = [];
|
||||
}
|
||||
|
||||
SetPolyfill.prototype.add = function(e) {
|
||||
if (this._cache.indexOf(e) === -1) {
|
||||
this._cache.push(e);
|
||||
}
|
||||
};
|
||||
|
||||
SetPolyfill.prototype.forEach = function(cb) {
|
||||
this._cache.forEach(cb);
|
||||
};
|
||||
|
||||
module.exports = SetPolyfill;
|
||||
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
'use strict';
|
||||
module.exports = function(style) {
|
||||
return style;
|
||||
};
|
||||
@@ -1,211 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule AppState
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const MissingNativeEventEmitterShim = require('MissingNativeEventEmitterShim');
|
||||
const NativeEventEmitter = require('NativeEventEmitter');
|
||||
const NativeModules = require('NativeModules');
|
||||
const RCTAppState = NativeModules.AppState;
|
||||
|
||||
const logError = require('logError');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
/**
|
||||
* `AppState` can tell you if the app is in the foreground or background,
|
||||
* and notify you when the state changes.
|
||||
*
|
||||
* AppState is frequently used to determine the intent and proper behavior when
|
||||
* handling push notifications.
|
||||
*
|
||||
* ### App States
|
||||
*
|
||||
* - `active` - The app is running in the foreground
|
||||
* - `background` - The app is running in the background. The user is either
|
||||
* in another app or on the home screen
|
||||
* - `inactive` - This is a state that occurs when transitioning between
|
||||
* foreground & background, and during periods of inactivity such as
|
||||
* entering the Multitasking view or in the event of an incoming call
|
||||
*
|
||||
* For more information, see
|
||||
* [Apple's documentation](https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/TheAppLifeCycle/TheAppLifeCycle.html)
|
||||
*
|
||||
* ### Basic Usage
|
||||
*
|
||||
* To see the current state, you can check `AppState.currentState`, which
|
||||
* will be kept up-to-date. However, `currentState` will be null at launch
|
||||
* while `AppState` retrieves it over the bridge.
|
||||
*
|
||||
* ```
|
||||
* import React, {Component} from 'react'
|
||||
* import {AppState, Text} from 'react-native'
|
||||
*
|
||||
* class AppStateExample extends Component {
|
||||
*
|
||||
* state = {
|
||||
* appState: AppState.currentState
|
||||
* }
|
||||
*
|
||||
* componentDidMount() {
|
||||
* AppState.addEventListener('change', this._handleAppStateChange);
|
||||
* }
|
||||
*
|
||||
* componentWillUnmount() {
|
||||
* AppState.removeEventListener('change', this._handleAppStateChange);
|
||||
* }
|
||||
*
|
||||
* _handleAppStateChange = (nextAppState) => {
|
||||
* if (this.state.appState.match(/inactive|background/) && nextAppState === 'active') {
|
||||
* console.log('App has come to the foreground!')
|
||||
* }
|
||||
* this.setState({appState: nextAppState});
|
||||
* }
|
||||
*
|
||||
* render() {
|
||||
* return (
|
||||
* <Text>Current state is: {this.state.appState}</Text>
|
||||
* );
|
||||
* }
|
||||
*
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* This example will only ever appear to say "Current state is: active" because
|
||||
* the app is only visible to the user when in the `active` state, and the null
|
||||
* state will happen only momentarily.
|
||||
*/
|
||||
|
||||
class AppState extends NativeEventEmitter {
|
||||
|
||||
_eventHandlers: Object;
|
||||
currentState: ?string;
|
||||
isAvailable: boolean = true;
|
||||
|
||||
constructor() {
|
||||
super(RCTAppState);
|
||||
|
||||
this.isAvailable = true;
|
||||
this._eventHandlers = {
|
||||
change: new Map(),
|
||||
memoryWarning: new Map(),
|
||||
};
|
||||
|
||||
// TODO: Remove the 'active' fallback after `initialAppState` is exported by
|
||||
// the Android implementation.
|
||||
this.currentState = RCTAppState.initialAppState || 'active';
|
||||
|
||||
let eventUpdated = false;
|
||||
|
||||
// TODO: this is a terrible solution - in order to ensure `currentState` prop
|
||||
// is up to date, we have to register an observer that updates it whenever
|
||||
// the state changes, even if nobody cares. We should just deprecate the
|
||||
// `currentState` property and get rid of this.
|
||||
this.addListener(
|
||||
'appStateDidChange',
|
||||
(appStateData) => {
|
||||
eventUpdated = true;
|
||||
this.currentState = appStateData.app_state;
|
||||
}
|
||||
);
|
||||
|
||||
// TODO: see above - this request just populates the value of `currentState`
|
||||
// when the module is first initialized. Would be better to get rid of the prop
|
||||
// and expose `getCurrentAppState` method directly.
|
||||
RCTAppState.getCurrentAppState(
|
||||
(appStateData) => {
|
||||
if (!eventUpdated) {
|
||||
this.currentState = appStateData.app_state;
|
||||
}
|
||||
},
|
||||
logError
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a handler to AppState changes by listening to the `change` event type
|
||||
* and providing the handler
|
||||
*
|
||||
* TODO: now that AppState is a subclass of NativeEventEmitter, we could deprecate
|
||||
* `addEventListener` and `removeEventListener` and just use `addListener` and
|
||||
* `listener.remove()` directly. That will be a breaking change though, as both
|
||||
* the method and event names are different (addListener events are currently
|
||||
* required to be globally unique).
|
||||
*/
|
||||
addEventListener(
|
||||
type: string,
|
||||
handler: Function
|
||||
) {
|
||||
invariant(
|
||||
['change', 'memoryWarning'].indexOf(type) !== -1,
|
||||
'Trying to subscribe to unknown event: "%s"', type
|
||||
);
|
||||
if (type === 'change') {
|
||||
this._eventHandlers[type].set(handler, this.addListener(
|
||||
'appStateDidChange',
|
||||
(appStateData) => {
|
||||
handler(appStateData.app_state);
|
||||
}
|
||||
));
|
||||
} else if (type === 'memoryWarning') {
|
||||
this._eventHandlers[type].set(handler, this.addListener(
|
||||
'memoryWarning',
|
||||
handler
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a handler by passing the `change` event type and the handler
|
||||
*/
|
||||
removeEventListener(
|
||||
type: string,
|
||||
handler: Function
|
||||
) {
|
||||
invariant(
|
||||
['change', 'memoryWarning'].indexOf(type) !== -1,
|
||||
'Trying to remove listener for unknown event: "%s"', type
|
||||
);
|
||||
if (!this._eventHandlers[type].has(handler)) {
|
||||
return;
|
||||
}
|
||||
this._eventHandlers[type].get(handler).remove();
|
||||
this._eventHandlers[type].delete(handler);
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__ && !RCTAppState) {
|
||||
class MissingNativeAppStateShim extends MissingNativeEventEmitterShim {
|
||||
constructor() {
|
||||
super('RCTAppState', 'AppState');
|
||||
}
|
||||
|
||||
get currentState(): ?string {
|
||||
this.throwMissingNativeModule();
|
||||
}
|
||||
|
||||
addEventListener(...args: Array<any>) {
|
||||
this.throwMissingNativeModule();
|
||||
}
|
||||
|
||||
removeEventListener(...args: Array<any>) {
|
||||
this.throwMissingNativeModule();
|
||||
}
|
||||
}
|
||||
|
||||
// This module depends on the native `RCTAppState` module. If you don't include it,
|
||||
// `AppState.isAvailable` will return `false`, and any method calls will throw.
|
||||
// We reassign the class variable to keep the autodoc generator happy.
|
||||
AppState = new MissingNativeAppStateShim();
|
||||
} else {
|
||||
AppState = new AppState();
|
||||
}
|
||||
|
||||
module.exports = AppState;
|
||||
@@ -1,30 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule BatchedBridge
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const MessageQueue = require('MessageQueue');
|
||||
|
||||
const BatchedBridge = new MessageQueue();
|
||||
|
||||
// Wire up the batched bridge on the global object so that we can call into it.
|
||||
// Ideally, this would be the inverse relationship. I.e. the native environment
|
||||
// provides this global directly with its script embedded. Then this module
|
||||
// would export it. A possible fix would be to trim the dependencies in
|
||||
// MessageQueue to its minimal features and embed that in the native runtime.
|
||||
|
||||
Object.defineProperty(global, '__fbBatchedBridge', {
|
||||
configurable: true,
|
||||
value: BatchedBridge,
|
||||
});
|
||||
|
||||
module.exports = BatchedBridge;
|
||||
@@ -1,413 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule MessageQueue
|
||||
* @flow
|
||||
* @format
|
||||
*/
|
||||
|
||||
/*eslint no-bitwise: 0*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const ErrorUtils = require('ErrorUtils');
|
||||
const Systrace = require('Systrace');
|
||||
|
||||
const deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
const stringifySafe = require('stringifySafe');
|
||||
|
||||
export type SpyData = {
|
||||
type: number,
|
||||
module: ?string,
|
||||
method: string | number,
|
||||
isSync: boolean,
|
||||
successCbId: number,
|
||||
failCbId: number,
|
||||
args: Array<any>,
|
||||
returnValue?: any,
|
||||
};
|
||||
|
||||
const TO_JS = 0;
|
||||
const TO_NATIVE = 1;
|
||||
|
||||
const MODULE_IDS = 0;
|
||||
const METHOD_IDS = 1;
|
||||
const PARAMS = 2;
|
||||
const MIN_TIME_BETWEEN_FLUSHES_MS = 5;
|
||||
|
||||
const TRACE_TAG_REACT_APPS = 1 << 17;
|
||||
|
||||
const DEBUG_INFO_LIMIT = 64;
|
||||
|
||||
// Work around an initialization order issue
|
||||
let JSTimers = null;
|
||||
|
||||
class MessageQueue {
|
||||
_lazyCallableModules: {[key: string]: (void) => Object};
|
||||
_queue: [Array<number>, Array<number>, Array<any>, number];
|
||||
_successCallbacks: Array<?Function>;
|
||||
_failureCallbacks: Array<?Function>;
|
||||
_callID: number;
|
||||
_inCall: number;
|
||||
_lastFlush: number;
|
||||
_eventLoopStartTime: number;
|
||||
|
||||
_debugInfo: Object;
|
||||
_remoteModuleTable: Object;
|
||||
_remoteMethodTable: Object;
|
||||
|
||||
__spy: ?(data: SpyData) => void;
|
||||
|
||||
constructor() {
|
||||
this._lazyCallableModules = {};
|
||||
this._queue = [[], [], [], 0];
|
||||
this._successCallbacks = [];
|
||||
this._failureCallbacks = [];
|
||||
this._callID = 0;
|
||||
this._lastFlush = 0;
|
||||
this._eventLoopStartTime = new Date().getTime();
|
||||
|
||||
if (__DEV__) {
|
||||
this._debugInfo = {};
|
||||
this._remoteModuleTable = {};
|
||||
this._remoteMethodTable = {};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Public APIs
|
||||
*/
|
||||
static spy(spyOrToggle: boolean | ((data: SpyData) => void)) {
|
||||
if (spyOrToggle === true) {
|
||||
MessageQueue.prototype.__spy = info => {
|
||||
console.log(
|
||||
`${info.type === TO_JS ? 'N->JS' : 'JS->N'} : ` +
|
||||
`${info.module ? info.module + '.' : ''}${info.method}` +
|
||||
`(${JSON.stringify(info.args)})`,
|
||||
);
|
||||
};
|
||||
} else if (spyOrToggle === false) {
|
||||
MessageQueue.prototype.__spy = null;
|
||||
} else {
|
||||
MessageQueue.prototype.__spy = spyOrToggle;
|
||||
}
|
||||
}
|
||||
|
||||
callFunctionReturnFlushedQueue = (
|
||||
module: string,
|
||||
method: string,
|
||||
args: Array<any>,
|
||||
) => {
|
||||
this.__guard(() => {
|
||||
this.__callFunction(module, method, args);
|
||||
});
|
||||
|
||||
return this.flushedQueue();
|
||||
};
|
||||
|
||||
callFunctionReturnResultAndFlushedQueue = (
|
||||
module: string,
|
||||
method: string,
|
||||
args: Array<any>,
|
||||
) => {
|
||||
let result;
|
||||
this.__guard(() => {
|
||||
result = this.__callFunction(module, method, args);
|
||||
});
|
||||
|
||||
return [result, this.flushedQueue()];
|
||||
};
|
||||
|
||||
invokeCallbackAndReturnFlushedQueue = (cbID: number, args: Array<any>) => {
|
||||
this.__guard(() => {
|
||||
this.__invokeCallback(cbID, args);
|
||||
});
|
||||
|
||||
return this.flushedQueue();
|
||||
};
|
||||
|
||||
flushedQueue = () => {
|
||||
this.__guard(() => {
|
||||
this.__callImmediates();
|
||||
});
|
||||
|
||||
const queue = this._queue;
|
||||
this._queue = [[], [], [], this._callID];
|
||||
return queue[0].length ? queue : null;
|
||||
};
|
||||
|
||||
getEventLoopRunningTime() {
|
||||
return new Date().getTime() - this._eventLoopStartTime;
|
||||
}
|
||||
|
||||
registerCallableModule(name: string, module: Object) {
|
||||
this._lazyCallableModules[name] = () => module;
|
||||
}
|
||||
|
||||
registerLazyCallableModule(name: string, factory: void => Object) {
|
||||
let module: Object;
|
||||
let getValue: ?(void) => Object = factory;
|
||||
this._lazyCallableModules[name] = () => {
|
||||
if (getValue) {
|
||||
module = getValue();
|
||||
getValue = null;
|
||||
}
|
||||
return module;
|
||||
};
|
||||
}
|
||||
|
||||
getCallableModule(name: string) {
|
||||
const getValue = this._lazyCallableModules[name];
|
||||
return getValue ? getValue() : null;
|
||||
}
|
||||
|
||||
enqueueNativeCall(
|
||||
moduleID: number,
|
||||
methodID: number,
|
||||
params: Array<any>,
|
||||
onFail: ?Function,
|
||||
onSucc: ?Function,
|
||||
) {
|
||||
if (onFail || onSucc) {
|
||||
if (__DEV__) {
|
||||
this._debugInfo[this._callID] = [moduleID, methodID];
|
||||
if (this._callID > DEBUG_INFO_LIMIT) {
|
||||
delete this._debugInfo[this._callID - DEBUG_INFO_LIMIT];
|
||||
}
|
||||
}
|
||||
// Encode callIDs into pairs of callback identifiers by shifting left and using the rightmost bit
|
||||
// to indicate fail (0) or success (1)
|
||||
onFail && params.push(this._callID << 1);
|
||||
onSucc && params.push((this._callID << 1) | 1);
|
||||
this._successCallbacks[this._callID] = onSucc;
|
||||
this._failureCallbacks[this._callID] = onFail;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
global.nativeTraceBeginAsyncFlow &&
|
||||
global.nativeTraceBeginAsyncFlow(
|
||||
TRACE_TAG_REACT_APPS,
|
||||
'native',
|
||||
this._callID,
|
||||
);
|
||||
}
|
||||
this._callID++;
|
||||
|
||||
this._queue[MODULE_IDS].push(moduleID);
|
||||
this._queue[METHOD_IDS].push(methodID);
|
||||
|
||||
if (__DEV__) {
|
||||
// Any params sent over the bridge should be encodable as JSON
|
||||
JSON.stringify(params);
|
||||
|
||||
// The params object should not be mutated after being queued
|
||||
deepFreezeAndThrowOnMutationInDev((params: any));
|
||||
}
|
||||
this._queue[PARAMS].push(params);
|
||||
|
||||
const now = new Date().getTime();
|
||||
if (
|
||||
global.nativeFlushQueueImmediate &&
|
||||
(now - this._lastFlush >= MIN_TIME_BETWEEN_FLUSHES_MS ||
|
||||
this._inCall === 0)
|
||||
) {
|
||||
var queue = this._queue;
|
||||
this._queue = [[], [], [], this._callID];
|
||||
this._lastFlush = now;
|
||||
global.nativeFlushQueueImmediate(queue);
|
||||
}
|
||||
Systrace.counterEvent('pending_js_to_native_queue', this._queue[0].length);
|
||||
|
||||
if (this.__spy) {
|
||||
this.__spyNativeCall(moduleID, methodID, params, {
|
||||
failCbId: onFail ? params[params.length - 2] : -1,
|
||||
successCbId: onSucc ? params[params.length - 1] : -1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
callSyncHook(moduleID: number, methodID: number, args: Array<any>) {
|
||||
if (__DEV__) {
|
||||
invariant(
|
||||
global.nativeCallSyncHook,
|
||||
'Calling synchronous methods on native ' +
|
||||
'modules is not supported in Chrome.\n\n Consider providing alternative ' +
|
||||
'methods to expose this method in debug mode, e.g. by exposing constants ' +
|
||||
'ahead-of-time.',
|
||||
);
|
||||
}
|
||||
const returnValue = global.nativeCallSyncHook(moduleID, methodID, args);
|
||||
if (this.__spy) {
|
||||
this.__spyNativeCall(moduleID, methodID, args, {
|
||||
isSync: true,
|
||||
returnValue,
|
||||
});
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
createDebugLookup(moduleID: number, name: string, methods: Array<string>) {
|
||||
if (__DEV__) {
|
||||
this._remoteModuleTable[moduleID] = name;
|
||||
this._remoteMethodTable[moduleID] = methods;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Private methods
|
||||
*/
|
||||
|
||||
__guard(fn: () => void) {
|
||||
this._inCall++;
|
||||
try {
|
||||
fn();
|
||||
} catch (error) {
|
||||
ErrorUtils.reportFatalError(error);
|
||||
} finally {
|
||||
this._inCall--;
|
||||
}
|
||||
}
|
||||
|
||||
__callImmediates() {
|
||||
Systrace.beginEvent('JSTimers.callImmediates()');
|
||||
if (!JSTimers) {
|
||||
JSTimers = require('JSTimers');
|
||||
}
|
||||
JSTimers.callImmediates();
|
||||
Systrace.endEvent();
|
||||
}
|
||||
|
||||
__callFunction(module: string, method: string, args: Array<any>) {
|
||||
this._lastFlush = new Date().getTime();
|
||||
this._eventLoopStartTime = this._lastFlush;
|
||||
Systrace.beginEvent(`${module}.${method}()`);
|
||||
if (this.__spy) {
|
||||
this.__spyJSCall(module, method, args);
|
||||
}
|
||||
const moduleMethods = this.getCallableModule(module);
|
||||
invariant(
|
||||
!!moduleMethods,
|
||||
'Module %s is not a registered callable module (calling %s)',
|
||||
module,
|
||||
method,
|
||||
);
|
||||
invariant(
|
||||
!!moduleMethods[method],
|
||||
'Method %s does not exist on module %s',
|
||||
method,
|
||||
module,
|
||||
);
|
||||
const result = moduleMethods[method].apply(moduleMethods, args);
|
||||
Systrace.endEvent();
|
||||
return result;
|
||||
}
|
||||
|
||||
__invokeCallback(cbID: number, args: Array<any>) {
|
||||
this._lastFlush = new Date().getTime();
|
||||
this._eventLoopStartTime = this._lastFlush;
|
||||
|
||||
// The rightmost bit of cbID indicates fail (0) or success (1), the other bits are the callID shifted left.
|
||||
const callID = cbID >>> 1;
|
||||
const isSuccess = cbID & 1;
|
||||
const callback = isSuccess
|
||||
? this._successCallbacks[callID]
|
||||
: this._failureCallbacks[callID];
|
||||
|
||||
if (__DEV__) {
|
||||
const debug = this._debugInfo[callID];
|
||||
const module = debug && this._remoteModuleTable[debug[0]];
|
||||
const method = debug && this._remoteMethodTable[debug[0]][debug[1]];
|
||||
if (!callback) {
|
||||
let errorMessage = `Callback with id ${cbID}: ${module}.${method}() not found`;
|
||||
if (method) {
|
||||
errorMessage =
|
||||
`The callback ${method}() exists in module ${module}, ` +
|
||||
'but only one callback may be registered to a function in a native module.';
|
||||
}
|
||||
invariant(callback, errorMessage);
|
||||
}
|
||||
const profileName = debug
|
||||
? '<callback for ' + module + '.' + method + '>'
|
||||
: cbID + '';
|
||||
if (this.__spy) {
|
||||
this.__spyJSCall(null, profileName, args, {
|
||||
failCbId: isSuccess ? -1 : cbID,
|
||||
successCbId: isSuccess ? cbID : -1,
|
||||
});
|
||||
}
|
||||
Systrace.beginEvent(
|
||||
`MessageQueue.invokeCallback(${profileName}, ${stringifySafe(args)})`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._successCallbacks[callID] = this._failureCallbacks[callID] = null;
|
||||
callback.apply(null, args);
|
||||
|
||||
if (__DEV__) {
|
||||
Systrace.endEvent();
|
||||
}
|
||||
}
|
||||
|
||||
__spyJSCall(
|
||||
module: ?string,
|
||||
method: string,
|
||||
methodArgs: Array<any>,
|
||||
params: any,
|
||||
) {
|
||||
if (!this.__spy) {
|
||||
return;
|
||||
}
|
||||
this.__spy({
|
||||
type: TO_JS,
|
||||
isSync: false,
|
||||
module,
|
||||
method,
|
||||
failCbId: -1,
|
||||
successCbId: -1,
|
||||
args: methodArgs,
|
||||
...params,
|
||||
});
|
||||
}
|
||||
|
||||
__spyNativeCall(
|
||||
moduleID: number,
|
||||
methodID: number,
|
||||
methodArgs: Array<any>,
|
||||
params: any,
|
||||
) {
|
||||
const spy = this.__spy;
|
||||
if (!spy) {
|
||||
return;
|
||||
}
|
||||
|
||||
let moduleName = moduleID + '';
|
||||
let methodName = methodID;
|
||||
if (__DEV__ && isFinite(moduleID)) {
|
||||
moduleName = this._remoteModuleTable[moduleID];
|
||||
methodName = this._remoteMethodTable[moduleID][methodID];
|
||||
}
|
||||
|
||||
spy({
|
||||
type: TO_NATIVE,
|
||||
isSync: false,
|
||||
module: moduleName,
|
||||
method: methodName,
|
||||
failCbId: -1,
|
||||
successCbId: -1,
|
||||
args: methodArgs,
|
||||
...params,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = MessageQueue;
|
||||
@@ -1,150 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @providesModule NativeModules
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const BatchedBridge = require('BatchedBridge');
|
||||
|
||||
const invariant = require('fbjs/lib/invariant');
|
||||
|
||||
import type {ExtendedError} from 'parseErrorStack';
|
||||
|
||||
type ModuleConfig = [
|
||||
string, /* name */
|
||||
?Object, /* constants */
|
||||
Array<string>, /* functions */
|
||||
Array<number>, /* promise method IDs */
|
||||
Array<number>, /* sync method IDs */
|
||||
];
|
||||
|
||||
export type MethodType = 'async' | 'promise' | 'sync';
|
||||
|
||||
function genModule(config: ?ModuleConfig, moduleID: number): ?{name: string, module?: Object} {
|
||||
if (!config) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [moduleName, constants, methods, promiseMethods, syncMethods] = config;
|
||||
invariant(!moduleName.startsWith('RCT') && !moduleName.startsWith('RK'),
|
||||
'Module name prefixes should\'ve been stripped by the native side ' +
|
||||
'but wasn\'t for ' + moduleName);
|
||||
|
||||
if (!constants && !methods) {
|
||||
// Module contents will be filled in lazily later
|
||||
return { name: moduleName };
|
||||
}
|
||||
|
||||
const module = {};
|
||||
methods && methods.forEach((methodName, methodID) => {
|
||||
const isPromise = promiseMethods && arrayContains(promiseMethods, methodID);
|
||||
const isSync = syncMethods && arrayContains(syncMethods, methodID);
|
||||
invariant(!isPromise || !isSync, 'Cannot have a method that is both async and a sync hook');
|
||||
const methodType = isPromise ? 'promise' : isSync ? 'sync' : 'async';
|
||||
module[methodName] = genMethod(moduleID, methodID, methodType);
|
||||
});
|
||||
Object.assign(module, constants);
|
||||
|
||||
if (__DEV__) {
|
||||
BatchedBridge.createDebugLookup(moduleID, moduleName, methods);
|
||||
}
|
||||
|
||||
return { name: moduleName, module };
|
||||
}
|
||||
|
||||
// export this method as a global so we can call it from native
|
||||
global.__fbGenNativeModule = genModule;
|
||||
|
||||
function loadModule(name: string, moduleID: number): ?Object {
|
||||
invariant(global.nativeRequireModuleConfig,
|
||||
'Can\'t lazily create module without nativeRequireModuleConfig');
|
||||
const config = global.nativeRequireModuleConfig(name);
|
||||
const info = genModule(config, moduleID);
|
||||
return info && info.module;
|
||||
}
|
||||
|
||||
function genMethod(moduleID: number, methodID: number, type: MethodType) {
|
||||
let fn = null;
|
||||
if (type === 'promise') {
|
||||
fn = function(...args: Array<any>) {
|
||||
return new Promise((resolve, reject) => {
|
||||
BatchedBridge.enqueueNativeCall(moduleID, methodID, args,
|
||||
(data) => resolve(data),
|
||||
(errorData) => reject(createErrorFromErrorData(errorData)));
|
||||
});
|
||||
};
|
||||
} else if (type === 'sync') {
|
||||
fn = function(...args: Array<any>) {
|
||||
return BatchedBridge.callSyncHook(moduleID, methodID, args);
|
||||
};
|
||||
} else {
|
||||
fn = function(...args: Array<any>) {
|
||||
const lastArg = args.length > 0 ? args[args.length - 1] : null;
|
||||
const secondLastArg = args.length > 1 ? args[args.length - 2] : null;
|
||||
const hasSuccessCallback = typeof lastArg === 'function';
|
||||
const hasErrorCallback = typeof secondLastArg === 'function';
|
||||
hasErrorCallback && invariant(
|
||||
hasSuccessCallback,
|
||||
'Cannot have a non-function arg after a function arg.'
|
||||
);
|
||||
const onSuccess = hasSuccessCallback ? lastArg : null;
|
||||
const onFail = hasErrorCallback ? secondLastArg : null;
|
||||
const callbackCount = hasSuccessCallback + hasErrorCallback;
|
||||
args = args.slice(0, args.length - callbackCount);
|
||||
BatchedBridge.enqueueNativeCall(moduleID, methodID, args, onFail, onSuccess);
|
||||
};
|
||||
}
|
||||
fn.type = type;
|
||||
return fn;
|
||||
}
|
||||
|
||||
function arrayContains<T>(array: Array<T>, value: T): boolean {
|
||||
return array.indexOf(value) !== -1;
|
||||
}
|
||||
|
||||
function createErrorFromErrorData(errorData: {message: string}): ExtendedError {
|
||||
const {
|
||||
message,
|
||||
...extraErrorInfo
|
||||
} = errorData || {};
|
||||
const error : ExtendedError = new Error(message);
|
||||
error.framesToPop = 1;
|
||||
return Object.assign(error, extraErrorInfo);
|
||||
}
|
||||
|
||||
let NativeModules : {[moduleName: string]: Object} = {};
|
||||
if (global.nativeModuleProxy) {
|
||||
NativeModules = global.nativeModuleProxy;
|
||||
} else {
|
||||
const bridgeConfig = global.__fbBatchedBridgeConfig;
|
||||
invariant(bridgeConfig, '__fbBatchedBridgeConfig is not set, cannot invoke native modules');
|
||||
|
||||
const defineLazyObjectProperty = require('defineLazyObjectProperty');
|
||||
(bridgeConfig.remoteModuleConfig || []).forEach((config: ModuleConfig, moduleID: number) => {
|
||||
// Initially this config will only contain the module name when running in JSC. The actual
|
||||
// configuration of the module will be lazily loaded.
|
||||
const info = genModule(config, moduleID);
|
||||
if (!info) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (info.module) {
|
||||
NativeModules[info.name] = info.module;
|
||||
}
|
||||
// If there's no module config, define a lazy getter
|
||||
else {
|
||||
defineLazyObjectProperty(NativeModules, info.name, {
|
||||
get: () => loadModule(info.name, moduleID)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = NativeModules;
|
||||
@@ -1,21 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* These don't actually exist anywhere in the code.
|
||||
*/
|
||||
'use strict';
|
||||
var remoteModulesConfig = [
|
||||
['RemoteModule1',null,['remoteMethod','promiseMethod'],[]],
|
||||
['RemoteModule2',null,['remoteMethod','promiseMethod'],[]],
|
||||
];
|
||||
|
||||
var MessageQueueTestConfig = {
|
||||
remoteModuleConfig: remoteModulesConfig,
|
||||
};
|
||||
|
||||
module.exports = MessageQueueTestConfig;
|
||||
@@ -1,24 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Dummy module that only exists for the sake of proving that the message queue
|
||||
* correctly dispatches to commonJS modules. The `testHook` is overriden by test
|
||||
* cases.
|
||||
*/
|
||||
var MessageQueueTestModule = {
|
||||
testHook1: function() {
|
||||
},
|
||||
testHook2: function() {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = MessageQueueTestModule;
|
||||
@@ -1,113 +0,0 @@
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
let MessageQueue;
|
||||
let MessageQueueTestModule;
|
||||
let queue;
|
||||
|
||||
const MODULE_IDS = 0;
|
||||
const METHOD_IDS = 1;
|
||||
const PARAMS = 2;
|
||||
|
||||
const assertQueue = (flushedQueue, index, moduleID, methodID, params) => {
|
||||
expect(flushedQueue[MODULE_IDS][index]).toEqual(moduleID);
|
||||
expect(flushedQueue[METHOD_IDS][index]).toEqual(methodID);
|
||||
expect(flushedQueue[PARAMS][index]).toEqual(params);
|
||||
};
|
||||
|
||||
// Important things to test:
|
||||
//
|
||||
// [x] Local modules can be invoked through the queue.
|
||||
//
|
||||
// [ ] Local modules that throw exceptions are gracefully caught. In that case
|
||||
// local callbacks stored by IDs are cleaned up.
|
||||
describe('MessageQueue', function() {
|
||||
beforeEach(function() {
|
||||
jest.resetModules();
|
||||
MessageQueue = require('MessageQueue');
|
||||
MessageQueueTestModule = require('MessageQueueTestModule');
|
||||
queue = new MessageQueue();
|
||||
queue.registerCallableModule(
|
||||
'MessageQueueTestModule',
|
||||
MessageQueueTestModule
|
||||
);
|
||||
queue.createDebugLookup(0, 'MessageQueueTestModule',
|
||||
['testHook1', 'testHook2']);
|
||||
});
|
||||
|
||||
it('should enqueue native calls', () => {
|
||||
queue.enqueueNativeCall(0, 1, [2]);
|
||||
const flushedQueue = queue.flushedQueue();
|
||||
assertQueue(flushedQueue, 0, 0, 1, [2]);
|
||||
});
|
||||
|
||||
it('should call a local function with the function name', () => {
|
||||
MessageQueueTestModule.testHook2 = jasmine.createSpy();
|
||||
expect(MessageQueueTestModule.testHook2.calls.count()).toEqual(0);
|
||||
queue.__callFunction('MessageQueueTestModule', 'testHook2', [2]);
|
||||
expect(MessageQueueTestModule.testHook2.calls.count()).toEqual(1);
|
||||
});
|
||||
|
||||
it('should store callbacks', () => {
|
||||
queue.enqueueNativeCall(0, 1, ['foo'], null, null);
|
||||
const flushedQueue = queue.flushedQueue();
|
||||
assertQueue(flushedQueue, 0, 0, 1, ['foo']);
|
||||
});
|
||||
|
||||
it('should call the stored callback', () => {
|
||||
let done = false;
|
||||
queue.enqueueNativeCall(0, 1, [], () => {}, () => { done = true; });
|
||||
queue.__invokeCallback(1);
|
||||
expect(done).toEqual(true);
|
||||
});
|
||||
|
||||
it('should throw when calling the same callback twice', () => {
|
||||
queue.enqueueNativeCall(0, 1, [], () => {}, () => {});
|
||||
queue.__invokeCallback(1);
|
||||
expect(() => queue.__invokeCallback(1)).toThrow();
|
||||
});
|
||||
|
||||
it('should throw when calling both success and failure callback', () => {
|
||||
queue.enqueueNativeCall(0, 1, [], () => {}, () => {});
|
||||
queue.__invokeCallback(1);
|
||||
expect(() => queue.__invokeCallback(0)).toThrow();
|
||||
});
|
||||
|
||||
it('should throw when calling with unknown module', () => {
|
||||
const unknownModule = 'UnknownModule', unknownMethod = 'UnknownMethod';
|
||||
expect(() => queue.__callFunction(unknownModule, unknownMethod)).toThrow(
|
||||
`Module ${unknownModule} is not a registered callable module (calling ${unknownMethod})`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return lazily registered module', () => {
|
||||
const dummyModule = {}, name = 'modulesName';
|
||||
queue.registerLazyCallableModule(name, () => dummyModule);
|
||||
|
||||
expect(queue.getCallableModule(name)).toEqual(dummyModule);
|
||||
});
|
||||
|
||||
it('should not initialize lazily registered module before it was used for the first time', () => {
|
||||
const dummyModule = {}, name = 'modulesName';
|
||||
const factory = jest.fn(() => dummyModule);
|
||||
queue.registerLazyCallableModule(name, factory);
|
||||
expect(factory).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should initialize lazily registered module only once', () => {
|
||||
const dummyModule = {}, name = 'modulesName';
|
||||
const factory = jest.fn(() => dummyModule);
|
||||
queue.registerLazyCallableModule(name, factory);
|
||||
queue.getCallableModule(name);
|
||||
queue.getCallableModule(name);
|
||||
expect(factory).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user