diff --git a/example/App.js b/example/App.js
index 38fb947..2107c21 100644
--- a/example/App.js
+++ b/example/App.js
@@ -4,6 +4,7 @@ import {StyleSheet, ScrollView, SafeAreaView} from 'react-native';
import Heart from './components/Heart';
import CustomShape from './components/CustomShape';
import CustomText from './components/CustomText';
+import AnimatedCircles from './components/AnimatedCircles';
export default function App() {
return (
@@ -12,6 +13,7 @@ export default function App() {
+
);
diff --git a/example/components/AnimatedCircles.js b/example/components/AnimatedCircles.js
new file mode 100644
index 0000000..31a77c0
--- /dev/null
+++ b/example/components/AnimatedCircles.js
@@ -0,0 +1,75 @@
+// @flow
+import React from 'react';
+import {Animated, Easing, StyleSheet, Dimensions} from 'react-native';
+import {Surface, Shape, Group} from '@react-native-community/art';
+
+/*
+An example of Animated Shapes
+
+Animated uses 'setNativeProps' on the AnimatedShape, preventing rerendering
+for each step in the animation
+*/
+
+const CIRCLE = 'M 25, 50 a 25,25 0 1,1 50,0 a 25,25 0 1,1 -50,0';
+
+export default function AnimatedCircles() {
+ const surfaceWidth = Dimensions.get('window').width;
+
+ const AnimatedShape = Animated.createAnimatedComponent(Shape);
+
+ const [animatedOpacity] = React.useState(new Animated.Value(1));
+
+ const blink = React.useCallback(
+ toValue =>
+ Animated.timing(animatedOpacity, {
+ duration: 900,
+ easing: Easing.linear,
+ toValue,
+ }).start(() => blink(toValue === 0 ? 1 : 0)),
+ [animatedOpacity],
+ );
+
+ React.useEffect(() => {
+ blink(0);
+ }, [blink]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+const styles = StyleSheet.create({
+ surface: {
+ backgroundColor: '#000',
+ },
+});
diff --git a/lib/Group.js b/lib/Group.js
index a4bb485..35cf71b 100644
--- a/lib/Group.js
+++ b/lib/Group.js
@@ -11,33 +11,46 @@ import * as React from 'react';
import PropTypes from 'prop-types';
import invariant from 'invariant';
import {NativeGroup} from './nativeComponents';
-import {extractOpacity, extractTransform, extractShadow} from './helpers';
+import {translatePropsToNativeProps} from './helpers';
import type {OpacityProps, TransformProps, ShadowProps} from './types';
-type GroupProps = OpacityProps &
+export type GroupProps = OpacityProps &
ShadowProps &
TransformProps & {
children: React.Node,
};
+const nativePropList = ['opacity', 'transform'];
+
export default class Group extends React.Component {
static contextTypes = {
isInSurface: PropTypes.bool.isRequired,
};
+ _rootComponent = null;
+
+ setNativeProps(newProps: $Shape) {
+ if (this._rootComponent && newProps) {
+ // newProps will only include what is changed, but we need existing props for some translations:
+ const nativeProps = translatePropsToNativeProps(
+ ({
+ ...this.props,
+ ...newProps,
+ }: GroupProps),
+ nativePropList,
+ );
+ // $FlowFixMe
+ this._rootComponent.setNativeProps(nativeProps);
+ }
+ }
+
render() {
invariant(
this.context.isInSurface,
'ART: must be a child of a ',
);
+ const nativeProps = translatePropsToNativeProps(this.props, nativePropList);
- return (
-
- {this.props.children}
-
- );
+ return {this.props.children};
}
}
diff --git a/lib/Shape.js b/lib/Shape.js
index a7c3dc1..fb5faca 100644
--- a/lib/Shape.js
+++ b/lib/Shape.js
@@ -10,16 +10,7 @@
import * as React from 'react';
import {NativeShape} from './nativeComponents';
import Path from './ARTSerializablePath';
-import {
- extractTransform,
- extractShadow,
- extractOpacity,
- childrenAsString,
- extractColor,
- extractStrokeJoin,
- extractStrokeCap,
- extractBrush,
-} from './helpers';
+import {translatePropsToNativeProps} from './helpers';
import type {
TransformProps,
ShadowProps,
@@ -45,6 +36,18 @@ export type ShapeProps = TransformProps &
height: number,
};
+const nativePropList = [
+ 'd',
+ 'fill',
+ 'opacity',
+ 'stroke',
+ 'strokeCap',
+ 'strokeDash',
+ 'strokeJoin',
+ 'strokeWidth',
+ 'transform',
+];
+
export default class Shape extends React.Component {
static defaultProps = {
strokeWidth: 1,
@@ -52,23 +55,30 @@ export default class Shape extends React.Component {
height: 0,
};
+ _rootComponent = null;
+
+ setNativeProps(newProps: $Shape) {
+ if (this._rootComponent && newProps) {
+ // newProps will only include what is changed, but we need existing props for some translations:
+ const nativeProps = translatePropsToNativeProps(
+ ({
+ ...this.props,
+ ...newProps,
+ }: ShapeProps),
+ nativePropList,
+ );
+ // $FlowFixMe
+ this._rootComponent.setNativeProps(nativeProps);
+ }
+ }
+
render() {
- const props = this.props;
- const path = props.d || childrenAsString(props.children);
- const d = (path instanceof Path ? path : new Path(path)).toJSON();
+ const nativeProps = translatePropsToNativeProps(this.props, nativePropList);
return (
(this._rootComponent = component)}
+ {...nativeProps}
/>
);
}
diff --git a/lib/Text.js b/lib/Text.js
index 5615a71..35d4c0a 100644
--- a/lib/Text.js
+++ b/lib/Text.js
@@ -10,18 +10,7 @@
import * as React from 'react';
import Path from './ARTSerializablePath';
import {NativeText} from './nativeComponents';
-import {
- extractBrush,
- extractOpacity,
- extractColor,
- extractStrokeCap,
- extractStrokeJoin,
- extractTransform,
- extractShadow,
- extractAlignment,
- childrenAsString,
- extractFontAndLines,
-} from './helpers';
+import {translatePropsToNativeProps} from './helpers';
import type {
TransformProps,
ShadowProps,
@@ -50,6 +39,20 @@ export type TextProps = TransformProps &
path?: string | Path,
};
+const nativePropList = [
+ 'alignment',
+ 'frame',
+ 'fill',
+ 'opacity',
+ 'path',
+ 'stroke',
+ 'strokeCap',
+ 'strokeDash',
+ 'strokeJoin',
+ 'strokeWidth',
+ 'transform',
+];
+
export default class Text extends React.Component {
static defaultProps = {
strokeWidth: 1,
@@ -57,30 +60,30 @@ export default class Text extends React.Component {
height: 0,
};
+ _rootComponent = null;
+
+ setNativeProps(newProps: $Shape) {
+ if (this._rootComponent && newProps) {
+ // newProps will only include what is changed, but we need existing props for some translations:
+ const nativeProps = translatePropsToNativeProps(
+ ({
+ ...this.props,
+ ...newProps,
+ }: TextProps),
+ nativePropList,
+ );
+ // $FlowFixMe
+ this._rootComponent.setNativeProps(nativeProps);
+ }
+ }
+
render() {
- const props = this.props;
- const path = props.path;
- const textPath = path
- ? (path instanceof Path ? path : new Path(path)).toJSON()
- : null;
- const textFrame = extractFontAndLines(
- props.font,
- childrenAsString(props.children),
- );
+ const nativeProps = translatePropsToNativeProps(this.props, nativePropList);
+
return (
(this._rootComponent = component)}
+ {...nativeProps}
/>
);
}
diff --git a/lib/__tests__/helpers.test.js b/lib/__tests__/helpers.test.js
index f787a73..49e520e 100644
--- a/lib/__tests__/helpers.test.js
+++ b/lib/__tests__/helpers.test.js
@@ -6,6 +6,7 @@ import {
extractStrokeJoin,
extractStrokeCap,
extractAlignment,
+ translatePropsToNativeProps,
} from '../helpers';
describe('testing childrenAsString function', () => {
@@ -78,3 +79,72 @@ describe('testing extractAlignment', () => {
expect(extractAlignment()).toBe(LEFT);
});
});
+
+describe('testing translatePropsToNativeProps', () => {
+ it('returns correct data for each prop', () => {
+ // asserting the values in the returned object will fail if any of our helpers
+ // are changed, but this is required to ensure the correct arguments are passed to
+ // each of them at the right key:
+ const props = {
+ alignment: 'center',
+ d: 'M 10,30 A 20,20 z',
+ fill: '#D9003A',
+ font: 'trebuchet',
+ height: 100,
+ opacity: 0.5,
+ stroke: '#2DCD71',
+ strokeCap: 'butt',
+ strokeDash: 2,
+ strokeJoin: 'miter',
+ strokeWidth: 3,
+ scale: 1.1,
+ width: 200,
+ };
+ const nativeProps1 = translatePropsToNativeProps(props, [
+ 'alignment',
+ 'd',
+ 'fill',
+ 'frame',
+ 'opacity',
+ 'stroke',
+ 'strokeCap',
+ 'strokeDash',
+ 'strokeJoin',
+ 'strokeWidth',
+ 'transform',
+ ]);
+ expect(nativeProps1).toEqual({
+ alignment: 2,
+ d: [0, 10, 30],
+ fill: [0, 0.8509803921568627, 0, 0.22745098039215686, 1],
+ frame: {
+ font: {
+ fontFamily: 'trebuchet',
+ fontSize: 12,
+ fontStyle: 'normal',
+ fontWeight: 'normal',
+ },
+ lines: [''],
+ },
+ opacity: 0.5,
+ stroke: '#2dcd71',
+ strokeCap: 0,
+ strokeDash: 2,
+ strokeJoin: 0,
+ strokeWidth: 3,
+ transform: [1.1, 0, 0, 1.1, 0, 0],
+ });
+
+ // test that we can select only a subset of nativeProps:
+ const nativeProps2 = translatePropsToNativeProps(props, [
+ 'fill',
+ 'opacity',
+ 'stroke',
+ ]);
+ expect(nativeProps2).toEqual({
+ fill: [0, 0.8509803921568627, 0, 0.22745098039215686, 1],
+ opacity: 0.5,
+ stroke: '#2dcd71',
+ });
+ });
+});
diff --git a/lib/helpers.js b/lib/helpers.js
index 4b30788..45a56c9 100644
--- a/lib/helpers.js
+++ b/lib/helpers.js
@@ -23,6 +23,10 @@ import type {
TransformProps,
ShadowProps,
} from './types';
+import Path from './ARTSerializablePath';
+import type {ShapeProps} from './Shape';
+import type {GroupProps} from './Group';
+import type {TextProps} from './Text';
export function childrenAsString(children?: string | Array) {
if (!children) {
@@ -355,3 +359,56 @@ export function insertDoubleColorStopsIntoArray(
lastIndex = insertOffsetsIntoArray(stops, targetArray, lastIndex, 0.5, false);
insertOffsetsIntoArray(stops, targetArray, lastIndex, 0.5, true);
}
+
+// a lookup of the methods we use to translate props to native props
+export const propsToNativePropsTranslations = {
+ alignment: ({alignment}: {alignment: Alignment}) =>
+ extractAlignment(alignment),
+ d: ({children, d}: {children?: string | Array, d: string | Path}) => {
+ const path = d || childrenAsString(children);
+ return (path instanceof Path ? path : new Path(path)).toJSON();
+ },
+ fill: ({
+ fill,
+ height,
+ width,
+ }: {
+ fill?: Brush | string,
+ height: number,
+ width: number,
+ }) => extractBrush(fill, {height, width}),
+ frame: ({
+ children,
+ font,
+ }: {
+ children?: string | Array,
+ font?: string | Font,
+ }) => extractFontAndLines(font, childrenAsString(children)),
+ opacity: extractOpacity,
+ shadow: extractShadow,
+ stroke: ({stroke}: {stroke: ColorType}) => extractColor(stroke),
+ strokeCap: ({strokeCap}: {strokeCap: StrokeCap}) =>
+ extractStrokeCap(strokeCap),
+ strokeDash: ({strokeDash}: {strokeDash: Array}) => strokeDash || null,
+ strokeJoin: ({strokeJoin}: {strokeJoin: StrokeJoin}) =>
+ extractStrokeJoin(strokeJoin),
+ strokeWidth: ({strokeWidth}: {strokeWidth: number}) => strokeWidth,
+ transform: extractTransform,
+ path: ({path}: {path: string | Path}) =>
+ path ? (path instanceof Path ? path : new Path(path)).toJSON() : null,
+};
+
+// take props, and an array of the required nativeProps, and return an object with
+// values for those nativeProps
+export function translatePropsToNativeProps(
+ props: GroupProps | ShapeProps | TextProps,
+ requiredProps: Array,
+) {
+ return requiredProps.reduce(
+ (nativeProps, prop) => ({
+ ...nativeProps,
+ [prop]: propsToNativePropsTranslations[prop](props),
+ }),
+ {},
+ );
+}