react-native/Libraries/StyleSheet/processTransform.js
Krzysztof Magiera 68b9a36858 Accept transforms list instead of matrix for transform view parameter.
Summary:
In #7916 I moved transform matrix decomposition logic from JS to java. The next step is to accept list of transforms instead oftransform matrix as a transform ReactProp. This way there is no extra processing required on JS side for the transform param (at least for android now) and this on the other hand allow us to execute transform updates (through offloaded animation) solely on the UI thread.

After this change there is a whole bunch of stuff from `Libraries/Utilities/MatrixMath.js` that can be deleted (methods like: determinant, inverse, transpose). Although astreet mentioned under one of my previous commits that the code is still being referenced internally at fb, so I decided not to delete it here.

**Test plan (required)**
Run UIExplorer Transform example before and after - compare the results
Run android unit test: com.facebook.react.uimanager.MatrixMathHelperTest
Closes https://github.com/facebook/react-native/pull/8892

Differential Revision: D3676017

Pulled By: astreet

fbshipit-source-id: 5275e30805a85c12c89bea44e8b3a2b2ec7b33fa
2016-08-05 10:58:42 -07:00

202 lines
6.0 KiB
JavaScript

/**
* 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 processTransform
* @flow
*/
'use strict';
var MatrixMath = require('MatrixMath');
var Platform = require('Platform');
var invariant = require('fbjs/lib/invariant');
var stringifySafe = require('stringifySafe');
/**
* Generate a transform matrix based on the provided transforms, and use that
* within the style object instead.
*
* This allows us to provide an API that is similar to CSS, where transforms may
* be applied in an arbitrary order, and yet have a universal, singular
* interface to native code.
*/
function processTransform(transform: Object): Object {
if (__DEV__) {
_validateTransforms(transform);
}
// Android implementation of transform property accepts the list of transform
// properties as opposed to a transform Matrix. This is necessary to control
// transform property updates completely on the native thread.
if (Platform.OS === 'android') {
return transform;
}
var result = MatrixMath.createIdentityMatrix();
transform.forEach(transformation => {
var key = Object.keys(transformation)[0];
var value = transformation[key];
switch (key) {
case 'matrix':
MatrixMath.multiplyInto(result, result, value);
break;
case 'perspective':
_multiplyTransform(result, MatrixMath.reusePerspectiveCommand, [value]);
break;
case 'rotateX':
_multiplyTransform(result, MatrixMath.reuseRotateXCommand, [_convertToRadians(value)]);
break;
case 'rotateY':
_multiplyTransform(result, MatrixMath.reuseRotateYCommand, [_convertToRadians(value)]);
break;
case 'rotate':
case 'rotateZ':
_multiplyTransform(result, MatrixMath.reuseRotateZCommand, [_convertToRadians(value)]);
break;
case 'scale':
_multiplyTransform(result, MatrixMath.reuseScaleCommand, [value]);
break;
case 'scaleX':
_multiplyTransform(result, MatrixMath.reuseScaleXCommand, [value]);
break;
case 'scaleY':
_multiplyTransform(result, MatrixMath.reuseScaleYCommand, [value]);
break;
case 'translate':
_multiplyTransform(result, MatrixMath.reuseTranslate3dCommand, [value[0], value[1], value[2] || 0]);
break;
case 'translateX':
_multiplyTransform(result, MatrixMath.reuseTranslate2dCommand, [value, 0]);
break;
case 'translateY':
_multiplyTransform(result, MatrixMath.reuseTranslate2dCommand, [0, value]);
break;
case 'skewX':
_multiplyTransform(result, MatrixMath.reuseSkewXCommand, [_convertToRadians(value)]);
break;
case 'skewY':
_multiplyTransform(result, MatrixMath.reuseSkewYCommand, [_convertToRadians(value)]);
break;
default:
throw new Error('Invalid transform name: ' + key);
}
});
return result;
}
/**
* Performs a destructive operation on a transform matrix.
*/
function _multiplyTransform(
result: Array<number>,
matrixMathFunction: Function,
args: Array<number>
): void {
var matrixToApply = MatrixMath.createIdentityMatrix();
var argsWithIdentity = [matrixToApply].concat(args);
matrixMathFunction.apply(this, argsWithIdentity);
MatrixMath.multiplyInto(result, result, matrixToApply);
}
/**
* Parses a string like '0.5rad' or '60deg' into radians expressed in a float.
* Note that validation on the string is done in `_validateTransform()`.
*/
function _convertToRadians(value: string): number {
var floatValue = parseFloat(value, 10);
return value.indexOf('rad') > -1 ? floatValue : floatValue * Math.PI / 180;
}
function _validateTransforms(transform: Object): void {
transform.forEach(transformation => {
var key = Object.keys(transformation)[0];
var value = transformation[key];
_validateTransform(key, value, transformation);
});
}
function _validateTransform(key, value, transformation) {
invariant(
!value.getValue,
'You passed an Animated.Value to a normal component. ' +
'You need to wrap that component in an Animated. For example, ' +
'replace <View /> by <Animated.View />.'
);
var multivalueTransforms = [
'matrix',
'translate',
];
if (multivalueTransforms.indexOf(key) !== -1) {
invariant(
Array.isArray(value),
'Transform with key of %s must have an array as the value: %s',
key,
stringifySafe(transformation),
);
}
switch (key) {
case 'matrix':
invariant(
value.length === 9 || value.length === 16,
'Matrix transform must have a length of 9 (2d) or 16 (3d). ' +
'Provided matrix has a length of %s: %s',
value.length,
stringifySafe(transformation),
);
break;
case 'translate':
break;
case 'rotateX':
case 'rotateY':
case 'rotateZ':
case 'rotate':
case 'skewX':
case 'skewY':
invariant(
typeof value === 'string',
'Transform with key of "%s" must be a string: %s',
key,
stringifySafe(transformation),
);
invariant(
value.indexOf('deg') > -1 || value.indexOf('rad') > -1,
'Rotate transform must be expressed in degrees (deg) or radians ' +
'(rad): %s',
stringifySafe(transformation),
);
break;
case 'perspective':
invariant(
typeof value === 'number',
'Transform with key of "%s" must be a number: %s',
key,
stringifySafe(transformation),
);
invariant(
value !== 0,
'Transform with key of "%s" cannot be zero: %s',
key,
stringifySafe(transformation),
);
break;
default:
invariant(
typeof value === 'number',
'Transform with key of "%s" must be a number: %s',
key,
stringifySafe(transformation),
);
}
}
module.exports = processTransform;