Skip to content

Commit db6ee6f

Browse files
committed
Initial support for ReactART
1 parent a0d5fc4 commit db6ee6f

File tree

12 files changed

+264
-19
lines changed

12 files changed

+264
-19
lines changed
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
/* @Flow */
2+
3+
import React from 'react';
4+
import ReactNative from 'react-native-desktop';
5+
6+
const {
7+
View,
8+
PanResponder,
9+
TouchableWithoutFeedback
10+
} = ReactNative;
11+
12+
var ReactART = require('ReactNativeART');
13+
14+
const {
15+
Shape,
16+
Group,
17+
Surface,
18+
Transform
19+
} = ReactART;
20+
21+
const layout = require('Dimensions').get('window');
22+
23+
class ErasingShape extends React.Component {
24+
25+
constructor() {
26+
super();
27+
28+
this.state = {
29+
svgPath: [],
30+
value: 0,
31+
letter: 'M',
32+
strokeWidth: 50
33+
};
34+
}
35+
36+
componentWillMount() {
37+
this._panResponder = PanResponder.create({
38+
// Ask to be the responder:
39+
onStartShouldSetPanResponder: (evt, gestureState) => true,
40+
onStartShouldSetPanResponderCapture: (evt, gestureState) => true,
41+
onMoveShouldSetPanResponder: (evt, gestureState) => true,
42+
onMoveShouldSetPanResponderCapture: (evt, gestureState) => true,
43+
44+
onPanResponderGrant: (evt, gestureState) => {
45+
// The guesture has started. Show visual feedback so the user knows
46+
// what is happening!
47+
//var letter = this.state.svgPath.length > 0 ? 'L' : 'M';
48+
this.state.svgPath.push(this.state.letter + Math.round(gestureState.x0) + ',' + Math.round(gestureState.y0));
49+
this.setState({svgPath: this.state.svgPath, letter: 'L'});
50+
// gestureState.{x,y}0 will be set to zero now
51+
},
52+
onPanResponderMove: (evt, gestureState) => {
53+
// The most recent move distance is gestureState.move{X,Y}
54+
this.state.svgPath.push(this.state.letter + Math.round(gestureState.moveX) + ',' + Math.round(gestureState.moveY));
55+
this.setState({svgPath: this.state.svgPath});
56+
// if (evt.nativeEvent.force && evt.nativeEvent.force > 0) {
57+
// this.setState({strokeWidth: 50 + 50 * (evt.nativeEvent.force / evt.nativeEvent.maxForce) })
58+
// }
59+
// The accumulated gesture distance since becoming responder is
60+
// gestureState.d{x,y}
61+
},
62+
onPanResponderTerminationRequest: (evt, gestureState) => true,
63+
onPanResponderRelease: (evt, gestureState) => {
64+
this.setState({letter: 'M'});
65+
// The user has released all touches while this view is the
66+
// responder. This typically means a gesture has succeeded
67+
},
68+
onPanResponderTerminate: (evt, gestureState) => {
69+
// Another component has become the responder, so this gesture
70+
// should be cancelled
71+
},
72+
onShouldBlockNativeResponder: (evt, gestureState) => {
73+
// Returns whether this component should block native components from becoming the JS
74+
// responder. Returns true by default. Is currently only supported on android.
75+
return true;
76+
},
77+
});
78+
}
79+
80+
render() {
81+
var {width, height} = layout;
82+
return (
83+
<View {...this._panResponder.panHandlers}
84+
style={[this.props.style, {flex: 1, top: 0, left: 0, height: 200, width: 300}]}>
85+
<Surface width={width} height={height}
86+
style={{backgroundColor: 'green', top: 0, left: 0, position: 'absolute'}}>
87+
<Shape d={this.state.svgPath.join('')}
88+
stroke="#88C76D"
89+
strokeWidth={this.state.strokeWidth} />
90+
91+
</Surface>
92+
</View>
93+
);
94+
}
95+
}
96+
97+
var MOUSE_UP_DRAG = 0.978;
98+
var MOUSE_DOWN_DRAG = 0.9;
99+
var MAX_VEL = 11;
100+
var CLICK_ACCEL = 3;
101+
var BASE_VEL = 0.15;
102+
/**
103+
* An animated SVG component.
104+
*/
105+
var VectorWidget = React.createClass({
106+
/**
107+
* Initialize state members.
108+
*/
109+
getInitialState: function() {
110+
return {degrees: 0, velocity: 0, drag: MOUSE_UP_DRAG};
111+
},
112+
/**
113+
* When the component is mounted into the document - this is similar to a
114+
* constructor, but invoked when the instance is actually mounted into the
115+
* document. Here's, we'll just set up an animation loop that invokes our
116+
* method. Binding of `this.onTick` is not needed because all React methods
117+
* are automatically bound before being mounted.
118+
*/
119+
componentDidMount: function() {
120+
this._interval = window.setInterval(this.onTick, 20);
121+
},
122+
componentWillUnmount: function() {
123+
window.clearInterval(this._interval);
124+
},
125+
onTick: function() {
126+
var nextDegrees = this.state.degrees + BASE_VEL + this.state.velocity;
127+
var nextVelocity = this.state.velocity * this.state.drag;
128+
this.setState({degrees: nextDegrees, velocity: nextVelocity});
129+
},
130+
/**
131+
* This is the "main" method for any component. The React API allows you to
132+
* describe the structure of your UI component at *any* point in time.
133+
*/
134+
render: function() {
135+
return (
136+
<Surface
137+
width={700}
138+
height={500}
139+
>
140+
{this.renderGraphic(this.state.degrees)}
141+
</Surface>
142+
);
143+
},
144+
renderGraphic: function(rotation) {
145+
return (
146+
<Group>
147+
<Group x={110} y={35}>
148+
<Shape fill="rgba(0,0,0,0.1)" d={BORDER_PATH} />
149+
<Shape fill="#7BC7BA" d={BG_PATH} />
150+
<Shape fill="#DCDCDC" d={BAR_PATH} />
151+
<Shape fill="#D97B76" d={RED_DOT_PATH} />
152+
<Shape fill="#DBBB79" d={YELLOW_DOT_PATH} />
153+
<Shape fill="#A6BD8A" d={GREEN_DOT_PATH} />
154+
<Group x={55} y={29}>
155+
<Group rotation={rotation} originX={84} originY={89}>
156+
<Shape fill="#FFFFFF" d={CENTER_DOT_PATH} />
157+
<Group>
158+
<Shape d={RING_ONE_PATH} stroke="#FFFFFF" strokeWidth={8} />
159+
<Shape d={RING_TWO_PATH} transform={RING_TWO_ROTATE} stroke="#FFFFFF" strokeWidth={8} />
160+
<Shape d={RING_THREE_PATH} transform={RING_THREE_ROTATE} stroke="#FFFFFF" strokeWidth={8} />
161+
</Group>
162+
</Group>
163+
</Group>
164+
</Group>
165+
</Group>
166+
);
167+
}
168+
});
169+
var BORDER_PATH = "M3.00191459,4 C1.34400294,4 0,5.34785514 0,7.00550479 L0,220.994495 C0,222.65439 1.34239483,224 3.00191459,224 L276.998085,224 C278.655997,224 280,222.652145 280,220.994495 L280,7.00550479 C280,5.34561033 278.657605,4 276.998085,4 L3.00191459,4 Z M3.00191459,4";
170+
var BG_PATH = "M3.00191459,1 C1.34400294,1 0,2.34785514 0,4.00550479 L0,217.994495 C0,219.65439 1.34239483,221 3.00191459,221 L276.998085,221 C278.655997,221 280,219.652145 280,217.994495 L280,4.00550479 C280,2.34561033 278.657605,1 276.998085,1 L3.00191459,1 Z M3.00191459,1";
171+
var BAR_PATH = "M3.00191459,0 C1.34400294,0 0,1.34559019 0,3.00878799 L0,21 C0,21 0,21 0,21 L280,21 C280,21 280,21 280,21 L280,3.00878799 C280,1.34708027 278.657605,0 276.998085,0 L3.00191459,0 Z M3.00191459,0";
172+
var RED_DOT_PATH = "M12.5,17 C16.0898511,17 19,14.0898511 19,10.5 C19,6.91014895 16.0898511,4 12.5,4 C8.91014895,4 6,6.91014895 6,10.5 C6,14.0898511 8.91014895,17 12.5,17 Z M12.5,17";
173+
var YELLOW_DOT_PATH = "M31.5,17 C35.0898511,17 38,14.0898511 38,10.5 C38,6.91014895 35.0898511,4 31.5,4 C27.9101489,4 25,6.91014895 25,10.5 C25,14.0898511 27.9101489,17 31.5,17 Z M31.5,17";
174+
var GREEN_DOT_PATH = "M50.5,17 C54.0898511,17 57,14.0898511 57,10.5 C57,6.91014895 54.0898511,4 50.5,4 C46.9101489,4 44,6.91014895 44,10.5 C44,14.0898511 46.9101489,17 50.5,17 Z M50.5,17";
175+
var CENTER_DOT_PATH = "M84,105 C92.8365564,105 100,97.8365564 100,89 C100,80.1634436 92.8365564,73 84,73 C75.1634436,73 68,80.1634436 68,89 C68,97.8365564 75.1634436,105 84,105 Z M84,105";
176+
var RING_ONE_PATH = "M84,121 C130.391921,121 168,106.673113 168,89 C168,71.3268871 130.391921,57 84,57 C37.6080787,57 0,71.3268871 0,89 C0,106.673113 37.6080787,121 84,121 Z M84,121";
177+
var RING_TWO_PATH = "M84,121 C130.391921,121 168,106.673113 168,89 C168,71.3268871 130.391921,57 84,57 C37.6080787,57 0,71.3268871 0,89 C0,106.673113 37.6080787,121 84,121 Z M84,121";
178+
var RING_THREE_PATH = "M84,121 C130.391921,121 168,106.673113 168,89 C168,71.3268871 130.391921,57 84,57 C37.6080787,57 0,71.3268871 0,89 C0,106.673113 37.6080787,121 84,121 Z M84,121";
179+
var RING_TWO_ROTATE = new Transform().translate(84.000000, 89.000000).rotate(-240.000000).translate(-84.000000, -89.000000);
180+
var RING_THREE_ROTATE = new Transform().translate(84.000000, 89.000000).rotate(-300.000000).translate(-84.000000, -89.000000);
181+
182+
183+
exports.title = 'ReactART';
184+
exports.description = 'React Native bridge to ART drawing library';
185+
exports.displayName = 'ReactART';
186+
exports.examples = [ {
187+
title: 'Erasing Shape',
188+
render: function () { return <ErasingShape />; }
189+
}, {
190+
title: 'Vector Widget',
191+
render: function() {return <VectorWidget />; }
192+
}];

Examples/UIExplorer/UIExplorer.xcodeproj/project.pbxproj

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
/* Begin PBXBuildFile section */
1010
031465071CAA70BB0005E5B6 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 031465061CAA6DF40005E5B6 /* libRCTLinking.a */; };
1111
031465081CAA71800005E5B6 /* libRCTLinking.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 031465061CAA6DF40005E5B6 /* libRCTLinking.a */; };
12+
0346750A1CEF5E7B009A625D /* libART.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 034675011CEF5D59009A625D /* libART.a */; };
1213
1300627F1B59179B0043FE5A /* RCTGzipTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1300627E1B59179B0043FE5A /* RCTGzipTests.m */; };
1314
13129DD41C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 13129DD31C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m */; };
1415
1323F1891C04AB9F0091BED0 /* bunny.png in Resources */ = {isa = PBXBuildFile; fileRef = 1323F1851C04AB9F0091BED0 /* bunny.png */; };
@@ -67,6 +68,13 @@
6768
remoteGlobalIDString = 134814201AA4EA6300B7C361;
6869
remoteInfo = RCTLinking;
6970
};
71+
034675001CEF5D59009A625D /* PBXContainerItemProxy */ = {
72+
isa = PBXContainerItemProxy;
73+
containerPortal = 034674FC1CEF5D59009A625D /* ART.xcodeproj */;
74+
proxyType = 2;
75+
remoteGlobalIDString = 0CF68AC11AF0540F00FF9E5C;
76+
remoteInfo = ART;
77+
};
7078
035D95A71C9352CC00B095A8 /* PBXContainerItemProxy */ = {
7179
isa = PBXContainerItemProxy;
7280
containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */;
@@ -135,6 +143,7 @@
135143
/* Begin PBXFileReference section */
136144
004D289E1AAF61C70097A701 /* UIExplorerUnitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UIExplorerUnitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
137145
031465011CAA6DF30005E5B6 /* RCTLinking.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RCTLinking.xcodeproj; path = ../../Libraries/LinkingIOS/RCTLinking.xcodeproj; sourceTree = "<group>"; };
146+
034674FC1CEF5D59009A625D /* ART.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = ART.xcodeproj; path = ../../Libraries/ART/ART.xcodeproj; sourceTree = "<group>"; };
138147
1300627E1B59179B0043FE5A /* RCTGzipTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTGzipTests.m; sourceTree = "<group>"; };
139148
13129DD31C85F87C007D611C /* RCTModuleInitNotificationRaceTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTModuleInitNotificationRaceTests.m; sourceTree = "<group>"; };
140149
1323F1851C04AB9F0091BED0 /* bunny.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = bunny.png; sourceTree = "<group>"; };
@@ -223,6 +232,7 @@
223232
isa = PBXFrameworksBuildPhase;
224233
buildActionMask = 2147483647;
225234
files = (
235+
0346750A1CEF5E7B009A625D /* libART.a in Frameworks */,
226236
031465081CAA71800005E5B6 /* libRCTLinking.a in Frameworks */,
227237
14AADF051AC3DBB1002390C9 /* libReact.a in Frameworks */,
228238
13417FE91AA91432003F314A /* libRCTImage.a in Frameworks */,
@@ -252,9 +262,18 @@
252262
name = Products;
253263
sourceTree = "<group>";
254264
};
265+
034674FD1CEF5D59009A625D /* Products */ = {
266+
isa = PBXGroup;
267+
children = (
268+
034675011CEF5D59009A625D /* libART.a */,
269+
);
270+
name = Products;
271+
sourceTree = "<group>";
272+
};
255273
1316A21D1AA397F400C0188E /* Libraries */ = {
256274
isa = PBXGroup;
257275
children = (
276+
034674FC1CEF5D59009A625D /* ART.xcodeproj */,
258277
14AADEFF1AC3DB95002390C9 /* React.xcodeproj */,
259278
13417FE31AA91428003F314A /* RCTImage.xcodeproj */,
260279
031465011CAA6DF30005E5B6 /* RCTLinking.xcodeproj */,
@@ -566,6 +585,10 @@
566585
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
567586
projectDirPath = "";
568587
projectReferences = (
588+
{
589+
ProductGroup = 034674FD1CEF5D59009A625D /* Products */;
590+
ProjectRef = 034674FC1CEF5D59009A625D /* ART.xcodeproj */;
591+
},
569592
{
570593
ProductGroup = 13417FE41AA91428003F314A /* Products */;
571594
ProjectRef = 13417FE31AA91428003F314A /* RCTImage.xcodeproj */;
@@ -616,6 +639,13 @@
616639
remoteRef = 031465051CAA6DF40005E5B6 /* PBXContainerItemProxy */;
617640
sourceTree = BUILT_PRODUCTS_DIR;
618641
};
642+
034675011CEF5D59009A625D /* libART.a */ = {
643+
isa = PBXReferenceProxy;
644+
fileType = archive.ar;
645+
path = libART.a;
646+
remoteRef = 034675001CEF5D59009A625D /* PBXContainerItemProxy */;
647+
sourceTree = BUILT_PRODUCTS_DIR;
648+
};
619649
13417FE81AA91428003F314A /* libRCTImage.a */ = {
620650
isa = PBXReferenceProxy;
621651
fileType = archive.ar;

Examples/UIExplorer/UIExplorerList.osx.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ var COMPONENTS = [
4747
require('./TransparentHitTestExample'),
4848
require('./ViewExample'),
4949
require('./WebViewExample'),
50+
require('./ReactARTExample'),
5051
];
5152

5253
var APIS = [

Libraries/ART/ART.xcodeproj/project.pbxproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@
283283
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
284284
MTL_ENABLE_DEBUG_INFO = YES;
285285
ONLY_ACTIVE_ARCH = YES;
286-
SDKROOT = iphoneos;
286+
SDKROOT = macosx;
287287
};
288288
name = Debug;
289289
};
@@ -321,7 +321,7 @@
321321
);
322322
IPHONEOS_DEPLOYMENT_TARGET = 7.0;
323323
MTL_ENABLE_DEBUG_INFO = NO;
324-
SDKROOT = iphoneos;
324+
SDKROOT = macosx;
325325
VALIDATE_PRODUCT = YES;
326326
};
327327
name = Release;

Libraries/ART/ARTNode.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,15 @@
88
*/
99

1010
#import <Foundation/Foundation.h>
11-
#import <UIKit/UIKit.h>
11+
#import <AppKit/AppKit.h>
1212

1313
/**
1414
* ART nodes are implemented as empty UIViews but this is just an implementation detail to fit
1515
* into the existing view management. They should also be shadow views and painted on a background
1616
* thread.
1717
*/
1818

19-
@interface ARTNode : UIView
19+
@interface ARTNode : NSView
2020

2121
@property (nonatomic, assign) CGFloat opacity;
2222

Libraries/ART/ARTNode.m

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,20 @@
1111

1212
#import "ARTContainer.h"
1313

14-
@implementation ARTNode
14+
@implementation ARTNode {
15+
CGAffineTransform _transform;
16+
//BOOL *_shouldBeTransformed;
17+
}
18+
19+
- (BOOL)isFlipped
20+
{
21+
return YES;
22+
}
1523

16-
- (void)insertSubview:(UIView *)subview atIndex:(NSInteger)index
24+
- (void)insertSubview:(NSView *)subview atIndex:(NSInteger)index
1725
{
1826
[self invalidate];
19-
[super insertSubview:subview atIndex:index];
27+
[super addSubview:subview];
2028
}
2129

2230
- (void)removeFromSuperview
@@ -34,7 +42,8 @@ - (void)setOpacity:(CGFloat)opacity
3442
- (void)setTransform:(CGAffineTransform)transform
3543
{
3644
[self invalidate];
37-
super.transform = transform;
45+
self.layer.affineTransform = _transform;
46+
_transform = transform;
3847
}
3948

4049
- (void)invalidate
@@ -52,15 +61,15 @@ - (void)renderTo:(CGContextRef)context
5261
if (self.opacity >= 1) {
5362
// Just paint at full opacity
5463
CGContextSaveGState(context);
55-
CGContextConcatCTM(context, self.transform);
64+
CGContextConcatCTM(context, self.layer.affineTransform);
5665
CGContextSetAlpha(context, 1);
5766
[self renderLayerTo:context];
5867
CGContextRestoreGState(context);
5968
return;
6069
}
6170
// This needs to be painted on a layer before being composited.
6271
CGContextSaveGState(context);
63-
CGContextConcatCTM(context, self.transform);
72+
CGContextConcatCTM(context, self.layer.affineTransform);
6473
CGContextSetAlpha(context, self.opacity);
6574
CGContextBeginTransparencyLayer(context, NULL);
6675
[self renderLayerTo:context];
@@ -73,4 +82,10 @@ - (void)renderLayerTo:(CGContextRef)context
7382
// abstract
7483
}
7584

85+
- (void)layout
86+
{
87+
[super layout];
88+
self.layer.affineTransform = _transform;
89+
}
90+
7691
@end

Libraries/ART/ARTRenderable.m

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ - (void)renderTo:(CGContextRef)context
7575
// This is a terminal with only one painting. Therefore we don't need to paint this
7676
// off-screen. We can just composite it straight onto the buffer.
7777
CGContextSaveGState(context);
78-
CGContextConcatCTM(context, self.transform);
78+
CGContextConcatCTM(context, self.layer.affineTransform);
7979
CGContextSetAlpha(context, self.opacity);
8080
[self renderLayerTo:context];
8181
CGContextRestoreGState(context);

0 commit comments

Comments
 (0)