Updates from Wed 29 Apr
|
@ -29,3 +29,6 @@ Examples/UIExplorer/ImageMocks.js
|
|||
|
||||
[options]
|
||||
module.system=haste
|
||||
|
||||
[version]
|
||||
0.10.0
|
||||
|
|
|
@ -57,6 +57,17 @@ var styles = StyleSheet.create({
|
|||
borderLeftWidth: 40,
|
||||
borderLeftColor: 'blue',
|
||||
},
|
||||
border5: {
|
||||
borderRadius: 50,
|
||||
borderTopWidth: 10,
|
||||
borderTopColor: 'red',
|
||||
borderRightWidth: 20,
|
||||
borderRightColor: 'yellow',
|
||||
borderBottomWidth: 30,
|
||||
borderBottomColor: 'green',
|
||||
borderLeftWidth: 40,
|
||||
borderLeftColor: 'blue',
|
||||
},
|
||||
});
|
||||
|
||||
exports.title = 'Border';
|
||||
|
@ -71,7 +82,7 @@ exports.examples = [
|
|||
},
|
||||
{
|
||||
title: 'Equal-Width / Same-Color',
|
||||
description: 'borderWidth & borderColor',
|
||||
description: 'borderWidth & borderColor & borderRadius',
|
||||
render() {
|
||||
return <View style={[styles.box, styles.borderRadius]} />;
|
||||
}
|
||||
|
@ -97,4 +108,11 @@ exports.examples = [
|
|||
return <View style={[styles.box, styles.border4]} />;
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Custom Borders',
|
||||
description: 'border*Width & border*Color',
|
||||
render() {
|
||||
return <View style={[styles.box, styles.border5]} />;
|
||||
}
|
||||
},
|
||||
];
|
||||
|
|
|
@ -50,7 +50,8 @@ var GeolocationExample = React.createClass({
|
|||
componentDidMount: function() {
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(initialPosition) => this.setState({initialPosition}),
|
||||
(error) => console.error(error)
|
||||
(error) => console.error(error),
|
||||
{enableHighAccuracy: true, timeout: 100, maximumAge: 1000}
|
||||
);
|
||||
this.watchID = navigator.geolocation.watchPosition((lastPosition) => {
|
||||
this.setState({lastPosition});
|
||||
|
|
|
@ -0,0 +1,169 @@
|
|||
/**
|
||||
* The examples provided by Facebook are for non-commercial testing and
|
||||
* evaluation purposes only.
|
||||
*
|
||||
* Facebook reserves all rights not expressly granted.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
|
||||
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
|
||||
* AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var React = require('react-native');
|
||||
var {
|
||||
SegmentedControlIOS,
|
||||
Text,
|
||||
View,
|
||||
StyleSheet
|
||||
} = React;
|
||||
|
||||
var BasicSegmentedControlExample = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<View style={{marginBottom: 10}}>
|
||||
<SegmentedControlIOS values={['One', 'Two']} />
|
||||
</View>
|
||||
<View>
|
||||
<SegmentedControlIOS values={['One', 'Two', 'Three', 'Four', 'Five']} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var PreSelectedSegmentedControlExample = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<View>
|
||||
<SegmentedControlIOS values={['One', 'Two']} selectedIndex={0} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var MomentarySegmentedControlExample = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<View>
|
||||
<SegmentedControlIOS values={['One', 'Two']} momentary={true} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var DisabledSegmentedControlExample = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<View>
|
||||
<SegmentedControlIOS enabled={false} values={['One', 'Two']} selectedIndex={1} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var ColorSegmentedControlExample = React.createClass({
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<View style={{marginBottom: 10}}>
|
||||
<SegmentedControlIOS tintColor="#ff0000" values={['One', 'Two', 'Three', 'Four']} selectedIndex={0} />
|
||||
</View>
|
||||
<View>
|
||||
<SegmentedControlIOS tintColor="#00ff00" values={['One', 'Two', 'Three']} selectedIndex={1} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var EventSegmentedControlExample = React.createClass({
|
||||
getInitialState() {
|
||||
return {
|
||||
values: ['One', 'Two', 'Three'],
|
||||
value: 'Not selected',
|
||||
selectedIndex: undefined
|
||||
};
|
||||
},
|
||||
|
||||
render() {
|
||||
return (
|
||||
<View>
|
||||
<Text style={styles.text} >
|
||||
Value: {this.state.value}
|
||||
</Text>
|
||||
<Text style={styles.text} >
|
||||
Index: {this.state.selectedIndex}
|
||||
</Text>
|
||||
<SegmentedControlIOS
|
||||
values={this.state.values}
|
||||
selectedIndex={this.state.selectedIndex}
|
||||
onChange={this._onChange}
|
||||
onValueChange={this._onValueChange} />
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
_onChange(event) {
|
||||
this.setState({
|
||||
selectedIndex: event.nativeEvent.selectedIndex,
|
||||
});
|
||||
},
|
||||
|
||||
_onValueChange(value) {
|
||||
this.setState({
|
||||
value: value,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
text: {
|
||||
fontSize: 14,
|
||||
textAlign: 'center',
|
||||
fontWeight: '500',
|
||||
margin: 10,
|
||||
},
|
||||
});
|
||||
|
||||
exports.title = '<SegmentedControlIOS>';
|
||||
exports.displayName = 'SegmentedControlExample';
|
||||
exports.description = 'Native segmented control';
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'Segmented controls can have values',
|
||||
render(): ReactElement { return <BasicSegmentedControlExample />; }
|
||||
},
|
||||
{
|
||||
title: 'Segmented controls can have a pre-selected value',
|
||||
render(): ReactElement { return <PreSelectedSegmentedControlExample />; }
|
||||
},
|
||||
{
|
||||
title: 'Segmented controls can be momentary',
|
||||
render(): ReactElement { return <MomentarySegmentedControlExample />; }
|
||||
},
|
||||
{
|
||||
title: 'Segmented controls can be disabled',
|
||||
render(): ReactElement { return <DisabledSegmentedControlExample />; }
|
||||
},
|
||||
{
|
||||
title: 'Custom colors can be provided',
|
||||
render(): ReactElement { return <ColorSegmentedControlExample />; }
|
||||
},
|
||||
{
|
||||
title: 'Change events can be detected',
|
||||
render(): ReactElement { return <EventSegmentedControlExample />; }
|
||||
}
|
||||
];
|
|
@ -88,9 +88,9 @@ var styles = StyleSheet.create({
|
|||
height: 26,
|
||||
borderWidth: 0.5,
|
||||
borderColor: '#0f0f0f',
|
||||
padding: 4,
|
||||
flex: 1,
|
||||
fontSize: 13,
|
||||
padding: 4,
|
||||
},
|
||||
multiline: {
|
||||
borderWidth: 0.5,
|
||||
|
@ -98,6 +98,22 @@ var styles = StyleSheet.create({
|
|||
flex: 1,
|
||||
fontSize: 13,
|
||||
height: 50,
|
||||
padding: 4,
|
||||
marginBottom: 4,
|
||||
},
|
||||
multilineWithFontStyles: {
|
||||
color: 'blue',
|
||||
fontWeight: 'bold',
|
||||
fontSize: 18,
|
||||
fontFamily: 'Cochin',
|
||||
height: 60,
|
||||
},
|
||||
multilineChild: {
|
||||
width: 50,
|
||||
height: 40,
|
||||
position: 'absolute',
|
||||
right: 5,
|
||||
backgroundColor: 'red',
|
||||
},
|
||||
eventLabel: {
|
||||
margin: 3,
|
||||
|
@ -118,7 +134,7 @@ var styles = StyleSheet.create({
|
|||
});
|
||||
|
||||
exports.title = '<TextInput>';
|
||||
exports.description = 'Single-line text inputs.';
|
||||
exports.description = 'Single and multi-line text inputs.';
|
||||
exports.examples = [
|
||||
{
|
||||
title: 'Auto-focus',
|
||||
|
@ -313,7 +329,7 @@ exports.examples = [
|
|||
},
|
||||
{
|
||||
title: 'Clear and select',
|
||||
render: function () {
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<WithLabel label="clearTextOnFocus">
|
||||
|
@ -336,4 +352,42 @@ exports.examples = [
|
|||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: 'Multiline',
|
||||
render: function() {
|
||||
return (
|
||||
<View>
|
||||
<TextInput
|
||||
placeholder="multiline text input"
|
||||
multiline={true}
|
||||
style={styles.multiline}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="mutliline text input with font styles and placeholder"
|
||||
multiline={true}
|
||||
clearTextOnFocus={true}
|
||||
autoCorrect={true}
|
||||
autoCapitalize="words"
|
||||
placeholderTextColor="red"
|
||||
keyboardType="url"
|
||||
style={[styles.multiline, styles.multilineWithFontStyles]}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="uneditable mutliline text input"
|
||||
editable={false}
|
||||
multiline={true}
|
||||
style={styles.multiline}
|
||||
/>
|
||||
<TextInput
|
||||
placeholder="multiline with children"
|
||||
multiline={true}
|
||||
enablesReturnKeyAutomatically={true}
|
||||
returnKeyType="go"
|
||||
style={styles.multiline}>
|
||||
<View style={styles.multilineChild}/>
|
||||
</TextInput>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
}
|
||||
];
|
||||
|
|
|
@ -70,6 +70,7 @@ var styles = StyleSheet.create({
|
|||
},
|
||||
titleContainer: {
|
||||
borderWidth: 0.5,
|
||||
borderRadius: 2.5,
|
||||
borderColor: '#d6d7da',
|
||||
backgroundColor: '#f6f7f8',
|
||||
paddingHorizontal: 10,
|
||||
|
@ -78,8 +79,10 @@ var styles = StyleSheet.create({
|
|||
titleRow: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
titleText: {
|
||||
backgroundColor: 'transparent',
|
||||
fontSize: 14,
|
||||
fontWeight: '500',
|
||||
},
|
||||
|
@ -97,6 +100,7 @@ var styles = StyleSheet.create({
|
|||
height: 8,
|
||||
},
|
||||
children: {
|
||||
backgroundColor: 'transparent',
|
||||
padding: 10,
|
||||
}
|
||||
});
|
||||
|
|
|
@ -43,6 +43,7 @@ var COMPONENTS = [
|
|||
require('./NavigatorIOSExample'),
|
||||
require('./PickerIOSExample'),
|
||||
require('./ScrollViewExample'),
|
||||
require('./SegmentedControlIOSExample'),
|
||||
require('./SliderIOSExample'),
|
||||
require('./SwitchIOSExample'),
|
||||
require('./TabBarIOSExample'),
|
||||
|
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 82 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 88 KiB |
Before Width: | Height: | Size: 260 KiB After Width: | Height: | Size: 264 KiB |
Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 88 KiB |
|
@ -94,6 +94,7 @@ var WebViewExample = React.createClass({
|
|||
automaticallyAdjustContentInsets={false}
|
||||
style={styles.webView}
|
||||
url={this.state.url}
|
||||
javaScriptEnabledAndroid={true}
|
||||
onNavigationStateChange={this.onNavigationStateChange}
|
||||
startInLoadingState={true}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,371 @@
|
|||
// !$*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 */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
0CF68ABF1AF0540F00FF9E5C /* 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>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
0CF68ABE1AF0540F00FF9E5C /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
0CF68AB81AF0540F00FF9E5C = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
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 */,
|
||||
0CF68AEA1AF0549300FF9E5C /* Brushes */,
|
||||
0CF68AF61AF0549300FF9E5C /* RCTConvert+ART.h */,
|
||||
0CF68AF71AF0549300FF9E5C /* RCTConvert+ART.m */,
|
||||
0CF68AF81AF0549300FF9E5C /* ViewManagers */,
|
||||
0CF68AC21AF0540F00FF9E5C /* Products */,
|
||||
);
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
0CF68AC21AF0540F00FF9E5C /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
0CF68AC11AF0540F00FF9E5C /* libART.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";
|
||||
};
|
||||
/* End PBXNativeTarget section */
|
||||
|
||||
/* Begin PBXProject section */
|
||||
0CF68AB91AF0540F00FF9E5C /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0620;
|
||||
TargetAttributes = {
|
||||
0CF68AC01AF0540F00FF9E5C = {
|
||||
CreatedOnToolsVersion = 6.2;
|
||||
};
|
||||
};
|
||||
};
|
||||
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 */,
|
||||
);
|
||||
};
|
||||
/* 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;
|
||||
};
|
||||
/* 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;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
"$(SRCROOT)/../../React/**",
|
||||
);
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.2;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
};
|
||||
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;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
|
||||
"$(SRCROOT)/../../React/**",
|
||||
);
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.2;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
0CF68AD61AF0540F00FF9E5C /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
0CF68AD71AF0540F00FF9E5C /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
OTHER_LDFLAGS = "-ObjC";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
};
|
||||
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;
|
||||
};
|
||||
/* End XCConfigurationList section */
|
||||
};
|
||||
rootObject = 0CF68AB91AF0540F00FF9E5C /* Project object */;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* 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;
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* 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
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "ARTContainer.h"
|
||||
#import "ARTNode.h"
|
||||
|
||||
@interface ARTGroup : ARTNode <ARTContainer>
|
||||
|
||||
@end
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* 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
|
||||
{
|
||||
// TO-DO: Clipping rectangle
|
||||
|
||||
for (ARTNode *node in self.subviews) {
|
||||
[node renderTo:context];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,33 @@
|
|||
/**
|
||||
* 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 <UIKit/UIKit.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
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* 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)insertSubview:(UIView *)subview atIndex:(NSInteger)index
|
||||
{
|
||||
[self invalidate];
|
||||
[super insertSubview:subview atIndex:index];
|
||||
}
|
||||
|
||||
- (void)removeFromSuperview
|
||||
{
|
||||
[self invalidate];
|
||||
[super removeFromSuperview];
|
||||
}
|
||||
|
||||
- (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
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* 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
|
|
@ -0,0 +1,89 @@
|
|||
/**
|
||||
* 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
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* 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;
|
|
@ -0,0 +1,18 @@
|
|||
/**
|
||||
* 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
|
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* 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
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "ARTContainer.h"
|
||||
|
||||
@interface ARTSurfaceView : UIView <ARTContainer>
|
||||
|
||||
@end
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* 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 "ARTNode.h"
|
||||
#import "RCTLog.h"
|
||||
|
||||
@implementation ARTSurfaceView
|
||||
|
||||
- (void)invalidate
|
||||
{
|
||||
[self setNeedsDisplay];
|
||||
}
|
||||
|
||||
- (void)drawRect:(CGRect)rect
|
||||
{
|
||||
CGContextRef context = UIGraphicsGetCurrentContext();
|
||||
for (ARTNode *node in self.subviews) {
|
||||
[node renderTo:context];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,20 @@
|
|||
/**
|
||||
* 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
|
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
- (void)setTextFrame:(ARTTextFrame)frame
|
||||
{
|
||||
if (frame.lines != _textFrame.lines && _textFrame.count) {
|
||||
// We must release each line before overriding the old one
|
||||
for (int i = 0; i < _textFrame.count; i++) {
|
||||
CFRelease(_textFrame.lines[0]);
|
||||
}
|
||||
free(_textFrame.lines);
|
||||
free(_textFrame.widths);
|
||||
}
|
||||
[self invalidate];
|
||||
_textFrame = frame;
|
||||
}
|
||||
|
||||
- (void)dealloc
|
||||
{
|
||||
if (_textFrame.count) {
|
||||
// We must release each line before freeing up this struct
|
||||
for (int i = 0; i < _textFrame.count; i++) {
|
||||
CFRelease(_textFrame.lines[0]);
|
||||
}
|
||||
free(_textFrame.lines);
|
||||
free(_textFrame.widths);
|
||||
}
|
||||
}
|
||||
|
||||
- (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
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* 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;
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* 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
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "ARTBrush.h"
|
||||
|
||||
@implementation ARTBrush
|
||||
|
||||
- (instancetype)initWithArray:(NSArray *)data
|
||||
{
|
||||
return [super init];
|
||||
}
|
||||
|
||||
- (BOOL)applyFillColor:(CGContextRef)context
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)paint:(CGContextRef)context
|
||||
{
|
||||
// abstract
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "ARTBrush.h"
|
||||
|
||||
@interface ARTLinearGradient : ARTBrush
|
||||
|
||||
@end
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* 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 "RCTConvert+ART.h"
|
||||
#import "RCTLog.h"
|
||||
|
||||
@implementation ARTLinearGradient
|
||||
{
|
||||
CGGradientRef _gradient;
|
||||
CGPoint _startPoint;
|
||||
CGPoint _endPoint;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray *)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
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "ARTBrush.h"
|
||||
|
||||
@interface ARTPattern : ARTBrush
|
||||
|
||||
@end
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* 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 "RCTConvert+ART.h"
|
||||
#import "RCTLog.h"
|
||||
|
||||
@implementation ARTPattern
|
||||
{
|
||||
CGImageRef _image;
|
||||
CGRect _rect;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray *)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
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "ARTBrush.h"
|
||||
|
||||
@interface ARTRadialGradient : ARTBrush
|
||||
|
||||
@end
|
|
@ -0,0 +1,56 @@
|
|||
/**
|
||||
* 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 "RCTConvert+ART.h"
|
||||
#import "RCTLog.h"
|
||||
|
||||
@implementation ARTRadialGradient
|
||||
{
|
||||
CGGradientRef _gradient;
|
||||
CGPoint _focusPoint;
|
||||
CGPoint _centerPoint;
|
||||
CGFloat _radius;
|
||||
CGFloat _radiusRatio;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray *)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
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "ARTBrush.h"
|
||||
|
||||
@interface ARTSolidColor : ARTBrush
|
||||
|
||||
@end
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* 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 "RCTConvert+ART.h"
|
||||
#import "RCTLog.h"
|
||||
|
||||
@implementation ARTSolidColor
|
||||
{
|
||||
CGColorRef _color;
|
||||
}
|
||||
|
||||
- (instancetype)initWithArray:(NSArray *)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
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* 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 "ARTBrush.h"
|
||||
#import "ARTCGFloatArray.h"
|
||||
#import "ARTTextFrame.h"
|
||||
#import "RCTConvert.h"
|
||||
|
||||
@interface RCTConvert (ART)
|
||||
|
||||
+ (CGPathRef)CGPath:(id)json;
|
||||
+ (CTFontRef)CTFont:(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
|
|
@ -0,0 +1,234 @@
|
|||
/**
|
||||
* 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 "ARTLinearGradient.h"
|
||||
#import "ARTPattern.h"
|
||||
#import "ARTRadialGradient.h"
|
||||
#import "ARTSolidColor.h"
|
||||
#import "RCTLog.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);
|
||||
}
|
||||
|
||||
+ (CTFontRef)CTFont:(id)json
|
||||
{
|
||||
NSDictionary *dict = [self NSDictionary:json];
|
||||
if (!dict) {
|
||||
return nil;
|
||||
}
|
||||
CTFontDescriptorRef fontDescriptor = CTFontDescriptorCreateWithAttributes((__bridge CFDictionaryRef)dict);
|
||||
CTFontRef font = CTFontCreateWithFontDescriptor(fontDescriptor, 0.0, NULL);
|
||||
CFRelease(fontDescriptor);
|
||||
return (CTFontRef)CFAutorelease(font);
|
||||
}
|
||||
|
||||
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 = [self CTFont: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[0]];
|
||||
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
|
|
@ -0,0 +1,611 @@
|
|||
/**
|
||||
* 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 ReactIOSART
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var Color = require('art/core/color');
|
||||
var Path = require('ARTSerializablePath');
|
||||
var Transform = require('art/core/transform');
|
||||
|
||||
var React = require('React');
|
||||
var ReactIOSViewAttributes = require('ReactIOSViewAttributes');
|
||||
|
||||
var createReactIOSNativeComponentClass = require('createReactIOSNativeComponentClass');
|
||||
var merge = require('merge');
|
||||
|
||||
// Diff Helpers
|
||||
|
||||
function arrayDiffer(a, b) {
|
||||
if (a == 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;
|
||||
}
|
||||
|
||||
var aTraits = a.font.NSCTFontTraitsAttribute;
|
||||
var bTraits = b.font.NSCTFontTraitsAttribute;
|
||||
|
||||
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 ||
|
||||
// TODO(6364240): remove iOS-specific attrs
|
||||
a.font.NSFontFamilyAttribute !== b.font.NSFontFamilyAttribute ||
|
||||
a.font.NSFontSizeAttribute !== b.font.NSFontSizeAttribute ||
|
||||
aTraits.NSCTFontSymbolicTrait !== bTraits.NSCTFontSymbolicTrait
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return arrayDiffer(a.lines, b.lines);
|
||||
}
|
||||
|
||||
// Native Attributes
|
||||
|
||||
var SurfaceViewAttributes = merge(ReactIOSViewAttributes.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 = createReactIOSNativeComponentClass({
|
||||
validAttributes: SurfaceViewAttributes,
|
||||
uiViewClassName: 'ARTSurfaceView',
|
||||
});
|
||||
|
||||
var NativeGroup = createReactIOSNativeComponentClass({
|
||||
validAttributes: GroupAttributes,
|
||||
uiViewClassName: 'ARTGroup',
|
||||
});
|
||||
|
||||
var NativeShape = createReactIOSNativeComponentClass({
|
||||
validAttributes: ShapeAttributes,
|
||||
uiViewClassName: 'ARTShape',
|
||||
});
|
||||
|
||||
var NativeText = createReactIOSNativeComponentClass({
|
||||
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
|
||||
|
||||
var Surface = React.createClass({
|
||||
|
||||
render: function() {
|
||||
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.
|
||||
|
||||
var Group = React.createClass({
|
||||
|
||||
render: function() {
|
||||
var props = this.props;
|
||||
return (
|
||||
<NativeGroup
|
||||
opacity={extractOpacity(props)}
|
||||
transform={extractTransform(props)}>
|
||||
{this.props.children}
|
||||
</NativeGroup>
|
||||
);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
var ClippingRectangle = React.createClass({
|
||||
|
||||
render: function() {
|
||||
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 = new Path()
|
||||
.moveTo(x, y)
|
||||
.line(w, 0)
|
||||
.line(0, h)
|
||||
.line(w, 0)
|
||||
.close()
|
||||
.toJSON();
|
||||
// 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.
|
||||
|
||||
var Shape = React.createClass({
|
||||
|
||||
render: function() {
|
||||
var props = this.props;
|
||||
var path = props.d || childrenAsString(props.children);
|
||||
var d = 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 = {};
|
||||
|
||||
function extractFontTraits(isBold, isItalic) {
|
||||
var italic = isItalic ? 1 : 0;
|
||||
var bold = isBold ? 2 : 0;
|
||||
return {
|
||||
NSCTFontSymbolicTrait: italic | bold
|
||||
};
|
||||
}
|
||||
|
||||
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',
|
||||
// TODO(6364240): remove iOS-specific attrs
|
||||
NSFontFamilyAttribute: fontFamily,
|
||||
NSFontSizeAttribute: fontSize,
|
||||
NSCTFontTraitsAttribute: extractFontTraits(isBold, isItalic)
|
||||
};
|
||||
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;
|
||||
return {
|
||||
// Normalize
|
||||
fontFamily: fontFamily,
|
||||
fontSize: fontSize,
|
||||
fontWeight: font.fontWeight,
|
||||
fontStyle: font.fontStyle,
|
||||
// TODO(6364240): remove iOS-specific attrs
|
||||
NSFontFamilyAttribute: fontFamily,
|
||||
NSFontSizeAttribute: fontSize,
|
||||
NSCTFontTraitsAttribute: extractFontTraits(
|
||||
font.fontWeight === 'bold',
|
||||
font.fontStyle === 'italic'
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
var Text = React.createClass({
|
||||
|
||||
render: function() {
|
||||
var props = this.props;
|
||||
var textPath = props.path ? new Path(props.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;
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "ARTNodeManager.h"
|
||||
|
||||
@interface ARTGroupManager : ARTNodeManager
|
||||
|
||||
@end
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* 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"
|
||||
|
||||
@implementation ARTGroupManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (ARTNode *)node
|
||||
{
|
||||
return [[ARTGroup alloc] init];
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "ARTNode.h"
|
||||
#import "RCTViewManager.h"
|
||||
|
||||
@interface ARTNodeManager : RCTViewManager
|
||||
|
||||
- (ARTNode *)node;
|
||||
|
||||
@end
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* 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 alloc] init];
|
||||
}
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [self node];
|
||||
}
|
||||
|
||||
- (RCTShadowView *)shadowView
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(opacity, CGFloat)
|
||||
RCT_EXPORT_VIEW_PROPERTY(transform, CGAffineTransform)
|
||||
|
||||
@end
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "ARTNodeManager.h"
|
||||
#import "ARTRenderable.h"
|
||||
|
||||
@interface ARTRenderableManager : ARTNodeManager
|
||||
|
||||
- (ARTRenderable *)node;
|
||||
|
||||
@end
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* 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 alloc] init];
|
||||
}
|
||||
|
||||
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
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "ARTRenderableManager.h"
|
||||
|
||||
@interface ARTShapeManager : ARTRenderableManager
|
||||
|
||||
@end
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* 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 alloc] init];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(d, CGPath)
|
||||
|
||||
@end
|
|
@ -9,6 +9,6 @@
|
|||
|
||||
#import "RCTViewManager.h"
|
||||
|
||||
@interface RCTUIActivityIndicatorViewManager : RCTViewManager
|
||||
@interface ARTSurfaceViewManager : RCTViewManager
|
||||
|
||||
@end
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* 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 alloc] init];
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "ARTRenderableManager.h"
|
||||
|
||||
@interface ARTTextManager : ARTRenderableManager
|
||||
|
||||
@end
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "ARTTextManager.h"
|
||||
|
||||
#import "ARTText.h"
|
||||
#import "RCTConvert+ART.h"
|
||||
|
||||
@implementation ARTTextManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (ARTRenderable *)node
|
||||
{
|
||||
return [[ARTText alloc] init];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(alignment, CTTextAlignment)
|
||||
RCT_REMAP_VIEW_PROPERTY(frame, textFrame, ARTTextFrame)
|
||||
|
||||
@end
|
|
@ -5,9 +5,9 @@
|
|||
|
||||
var NativeModules = {
|
||||
I18n: {
|
||||
translationsDictionary: {
|
||||
translationsDictionary: JSON.stringify({
|
||||
'Good bye, {name}!|Bye message': '¡Adiós {name}!',
|
||||
},
|
||||
}),
|
||||
},
|
||||
Timing: {
|
||||
createTimer: jest.genMockFunction(),
|
||||
|
@ -29,6 +29,7 @@ var NativeModules = {
|
|||
UIManager: {
|
||||
customBubblingEventTypes: {},
|
||||
customDirectEventTypes: {},
|
||||
Dimensions: {},
|
||||
},
|
||||
AsyncLocalStorage: {
|
||||
getItem: jest.genMockFunction(),
|
||||
|
|
|
@ -18,21 +18,16 @@ var React = require('React');
|
|||
var StyleSheet = require('StyleSheet');
|
||||
var View = require('View');
|
||||
|
||||
var keyMirror = require('keyMirror');
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
var verifyPropTypes = require('verifyPropTypes');
|
||||
|
||||
var SpinnerSize = keyMirror({
|
||||
large: null,
|
||||
small: null,
|
||||
});
|
||||
|
||||
var GRAY = '#999999';
|
||||
|
||||
type DefaultProps = {
|
||||
animating: boolean;
|
||||
size: 'small' | 'large';
|
||||
color: string;
|
||||
hidesWhenStopped: boolean;
|
||||
size: 'small' | 'large';
|
||||
};
|
||||
|
||||
var ActivityIndicatorIOS = React.createClass({
|
||||
|
@ -47,7 +42,10 @@ var ActivityIndicatorIOS = React.createClass({
|
|||
* The foreground color of the spinner (default is gray).
|
||||
*/
|
||||
color: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Whether the indicator should hide when not animating (true by default).
|
||||
*/
|
||||
hidesWhenStopped: PropTypes.bool,
|
||||
/**
|
||||
* Size of the indicator. Small has a height of 20, large has a height of 36.
|
||||
*/
|
||||
|
@ -60,27 +58,18 @@ var ActivityIndicatorIOS = React.createClass({
|
|||
getDefaultProps: function(): DefaultProps {
|
||||
return {
|
||||
animating: true,
|
||||
size: SpinnerSize.small,
|
||||
color: GRAY,
|
||||
hidesWhenStopped: true,
|
||||
size: 'small',
|
||||
};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
var style = styles.sizeSmall;
|
||||
var NativeConstants = NativeModules.UIManager.UIActivityIndicatorView.Constants;
|
||||
var activityIndicatorViewStyle = NativeConstants.StyleWhite;
|
||||
if (this.props.size === 'large') {
|
||||
style = styles.sizeLarge;
|
||||
activityIndicatorViewStyle = NativeConstants.StyleWhiteLarge;
|
||||
}
|
||||
var {style, ...props} = this.props;
|
||||
var sizeStyle = (this.props.size === 'large') ? styles.sizeLarge : styles.sizeSmall;
|
||||
return (
|
||||
<View
|
||||
style={[styles.container, style, this.props.style]}>
|
||||
<UIActivityIndicatorView
|
||||
activityIndicatorViewStyle={activityIndicatorViewStyle}
|
||||
animating={this.props.animating}
|
||||
color={this.props.color}
|
||||
/>
|
||||
<View style={[styles.container, sizeStyle, style]}>
|
||||
<RCTActivityIndicatorView {...props} />
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -99,15 +88,15 @@ var styles = StyleSheet.create({
|
|||
}
|
||||
});
|
||||
|
||||
var UIActivityIndicatorView = requireNativeComponent(
|
||||
'UIActivityIndicatorView',
|
||||
var RCTActivityIndicatorView = requireNativeComponent(
|
||||
'RCTActivityIndicatorView',
|
||||
null
|
||||
);
|
||||
if (__DEV__) {
|
||||
var nativeOnlyProps = {activityIndicatorViewStyle: true};
|
||||
verifyPropTypes(
|
||||
ActivityIndicatorIOS,
|
||||
UIActivityIndicatorView.viewConfig,
|
||||
RCTActivityIndicatorView.viewConfig,
|
||||
nativeOnlyProps
|
||||
);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
|
||||
/**
|
||||
* 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 SegmentedControlIOS
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React = require('React');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var Text = require('Text');
|
||||
var View = require('View');
|
||||
|
||||
var Dummy = React.createClass({
|
||||
render: function() {
|
||||
return (
|
||||
<View style={[styles.dummy, this.props.style]}>
|
||||
<Text style={styles.text}>
|
||||
SegmentedControlIOS is not supported on this platform!
|
||||
</Text>
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
dummy: {
|
||||
width: 120,
|
||||
height: 50,
|
||||
backgroundColor: '#ffbcbc',
|
||||
borderWidth: 1,
|
||||
borderColor: 'red',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
text: {
|
||||
color: '#333333',
|
||||
margin: 5,
|
||||
fontSize: 10,
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = Dummy;
|
|
@ -0,0 +1,120 @@
|
|||
/**
|
||||
* 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 SegmentedControlIOS
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var NativeMethodsMixin = require('NativeMethodsMixin');
|
||||
var NativeModules = require('NativeModules');
|
||||
var PropTypes = require('ReactPropTypes');
|
||||
var React = require('React');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
|
||||
var requireNativeComponent = require('requireNativeComponent');
|
||||
var verifyPropTypes = require('verifyPropTypes');
|
||||
|
||||
type DefaultProps = {
|
||||
values: Array<string>;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
var SEGMENTED_CONTROL_REFERENCE = 'segmentedcontrol';
|
||||
|
||||
type Event = Object;
|
||||
|
||||
/**
|
||||
* Use `SegmentedControlIOS` to render a UISegmentedControl iOS.
|
||||
*/
|
||||
var SegmentedControlIOS = React.createClass({
|
||||
mixins: [NativeMethodsMixin],
|
||||
|
||||
propTypes: {
|
||||
/**
|
||||
* The labels for the control's segment buttons, in order.
|
||||
*/
|
||||
values: PropTypes.arrayOf(PropTypes.string),
|
||||
|
||||
/**
|
||||
* The index in `props.values` of the segment to be pre-selected
|
||||
*/
|
||||
selectedIndex: PropTypes.number,
|
||||
|
||||
/**
|
||||
* Callback that is called when the user taps a segment;
|
||||
* passes the segment's value as an argument
|
||||
*/
|
||||
onValueChange: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Callback that is called when the user taps a segment;
|
||||
* passes the event as an argument
|
||||
*/
|
||||
onChange: PropTypes.func,
|
||||
|
||||
/**
|
||||
* If false the user won't be able to interact with the control.
|
||||
* Default value is true.
|
||||
*/
|
||||
enabled: PropTypes.bool,
|
||||
|
||||
/**
|
||||
* Accent color of the control.
|
||||
*/
|
||||
tintColor: PropTypes.string,
|
||||
|
||||
/**
|
||||
* If true, then selecting a segment won't persist visually.
|
||||
* The `onValueChange` callback will still work as expected.
|
||||
*/
|
||||
momentary: PropTypes.bool
|
||||
},
|
||||
|
||||
getDefaultProps: function(): DefaultProps {
|
||||
return {
|
||||
values: [],
|
||||
enabled: true
|
||||
};
|
||||
},
|
||||
|
||||
_onChange: function(event: Event) {
|
||||
this.props.onChange && this.props.onChange(event);
|
||||
this.props.onValueChange && this.props.onValueChange(event.nativeEvent.value);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return (
|
||||
<RCTSegmentedControl
|
||||
{...this.props}
|
||||
ref={SEGMENTED_CONTROL_REFERENCE}
|
||||
style={[styles.segmentedControl, this.props.style]}
|
||||
onChange={this._onChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
segmentedControl: {
|
||||
height: NativeModules.SegmentedControlManager.ComponentHeight
|
||||
},
|
||||
});
|
||||
|
||||
var RCTSegmentedControl = requireNativeComponent(
|
||||
'RCTSegmentedControl',
|
||||
null
|
||||
);
|
||||
if (__DEV__) {
|
||||
verifyPropTypes(
|
||||
RCTSegmentedControl,
|
||||
RCTSegmentedControl.viewConfig
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = SegmentedControlIOS;
|
|
@ -66,6 +66,9 @@ var TabBarItemIOS = React.createClass({
|
|||
* blank content, you probably forgot to add a selected one.
|
||||
*/
|
||||
selected: React.PropTypes.bool,
|
||||
/**
|
||||
* React style object.
|
||||
*/
|
||||
style: View.propTypes.style,
|
||||
/**
|
||||
* Text that appears under the icon. It is ignored when a system icon
|
||||
|
|
|
@ -38,6 +38,7 @@ var returnKeyTypeConsts = RCTUIManager.UIReturnKeyType;
|
|||
var RCTTextViewAttributes = merge(ReactIOSViewAttributes.UIView, {
|
||||
autoCorrect: true,
|
||||
autoCapitalize: true,
|
||||
clearTextOnFocus: true,
|
||||
color: true,
|
||||
editable: true,
|
||||
fontFamily: true,
|
||||
|
@ -48,6 +49,7 @@ var RCTTextViewAttributes = merge(ReactIOSViewAttributes.UIView, {
|
|||
returnKeyType: true,
|
||||
enablesReturnKeyAutomatically: true,
|
||||
secureTextEntry: true,
|
||||
selectTextOnFocus: true,
|
||||
mostRecentEventCounter: true,
|
||||
placeholder: true,
|
||||
placeholderTextColor: true,
|
||||
|
@ -445,6 +447,7 @@ var TextInput = React.createClass({
|
|||
onSubmitEditing={this.props.onSubmitEditing}
|
||||
onSelectionChangeShouldSetResponder={() => true}
|
||||
placeholder={this.props.placeholder}
|
||||
placeholderTextColor={this.props.placeholderTextColor}
|
||||
text={this.state.bufferedValue}
|
||||
autoCapitalize={autoCapitalize}
|
||||
autoCorrect={this.props.autoCorrect}
|
||||
|
@ -498,6 +501,8 @@ var TextInput = React.createClass({
|
|||
autoCapitalize={autoCapitalize}
|
||||
autoCorrect={this.props.autoCorrect}
|
||||
clearButtonMode={clearButtonMode}
|
||||
selectTextOnFocus={this.props.selectTextOnFocus}
|
||||
clearTextOnFocus={this.props.clearTextOnFocus}
|
||||
/>;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ var stylePropType = StyleSheetPropType(ViewStylePropTypes);
|
|||
* container that supports layout with flexbox, style, some touch handling, and
|
||||
* accessibility controls, and is designed to be nested inside other views and
|
||||
* to have 0 to many children of any type. `View` maps directly to the native
|
||||
* view equivalent on whatever platform react is running on, whether that is a
|
||||
* view equivalent on whatever platform React is running on, whether that is a
|
||||
* `UIView`, `<div>`, `android.view`, etc. This example creates a `View` that
|
||||
* wraps two colored boxes and custom component in a row with padding.
|
||||
*
|
||||
|
|
|
@ -42,6 +42,7 @@ var WebView = React.createClass({
|
|||
onNavigationStateChange: PropTypes.func,
|
||||
startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load
|
||||
style: View.propTypes.style,
|
||||
javaScriptEnabledAndroid: PropTypes.bool,
|
||||
/**
|
||||
* Used to locate this view in end-to-end tests.
|
||||
*/
|
||||
|
@ -90,6 +91,7 @@ var WebView = React.createClass({
|
|||
key="webViewKey"
|
||||
style={webViewStyles}
|
||||
url={this.props.url}
|
||||
javaScriptEnabledAndroid={this.props.javaScriptEnabledAndroid}
|
||||
contentInset={this.props.contentInset}
|
||||
automaticallyAdjustContentInsets={this.props.automaticallyAdjustContentInsets}
|
||||
onLoadingStart={this.onLoadingStart}
|
||||
|
@ -157,6 +159,7 @@ var WebView = React.createClass({
|
|||
var RCTWebView = createReactIOSNativeComponentClass({
|
||||
validAttributes: merge(ReactIOSViewAttributes.UIView, {
|
||||
url: true,
|
||||
javaScriptEnabledAndroid: true,
|
||||
}),
|
||||
uiViewClassName: 'RCTWebView',
|
||||
});
|
||||
|
|
|
@ -92,6 +92,10 @@ var WebView = React.createClass({
|
|||
onNavigationStateChange: PropTypes.func,
|
||||
startInLoadingState: PropTypes.bool, // force WebView to show loadingView on first load
|
||||
style: View.propTypes.style,
|
||||
/**
|
||||
* Used for android only, JS is enabled by default for WebView on iOS
|
||||
*/
|
||||
javaScriptEnabledAndroid: PropTypes.bool,
|
||||
},
|
||||
|
||||
getInitialState: function() {
|
||||
|
|
|
@ -267,7 +267,8 @@ var Navigator = React.createClass({
|
|||
},
|
||||
|
||||
contextTypes: {
|
||||
navigator: PropTypes.object,
|
||||
// TODO (t6707746) Re-enable this when owner context switches to parent context
|
||||
// navigator: PropTypes.object,
|
||||
},
|
||||
|
||||
statics: {
|
||||
|
@ -314,13 +315,11 @@ var Navigator = React.createClass({
|
|||
// On first render, we will render every scene in the initialRouteStack
|
||||
updatingRangeStart: 0,
|
||||
updatingRangeLength: routeStack.length,
|
||||
// Either animating or gesturing.
|
||||
isAnimating: false,
|
||||
jumpToIndex: routeStack.length - 1,
|
||||
presentedIndex: initialRouteIndex,
|
||||
isResponderOnlyToBlockTouches: false,
|
||||
fromIndex: initialRouteIndex,
|
||||
toIndex: initialRouteIndex,
|
||||
transitionFromIndex: null,
|
||||
activeGesture: null,
|
||||
pendingGestureProgress: null,
|
||||
transitionQueue: [],
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -355,9 +354,24 @@ var Navigator = React.createClass({
|
|||
popToTop: this.popToTop,
|
||||
};
|
||||
this._handlers = {};
|
||||
|
||||
this.springSystem = new rebound.SpringSystem();
|
||||
this.spring = this.springSystem.createSpring();
|
||||
this.spring.setRestSpeedThreshold(0.05);
|
||||
this.spring.setCurrentValue(0).setAtRest();
|
||||
this.spring.addListener({
|
||||
onSpringEndStateChange: () => {
|
||||
if (!this._interactionHandle) {
|
||||
this._interactionHandle = this.createInteractionHandle();
|
||||
}
|
||||
},
|
||||
onSpringUpdate: () => {
|
||||
this._handleSpringUpdate();
|
||||
},
|
||||
onSpringAtRest: () => {
|
||||
this._completeTransition();
|
||||
},
|
||||
});
|
||||
this.panGesture = PanResponder.create({
|
||||
onStartShouldSetPanResponderCapture: this._handleStartShouldSetPanResponderCapture,
|
||||
onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder,
|
||||
onPanResponderGrant: this._handlePanResponderGrant,
|
||||
onPanResponderRelease: this._handlePanResponderRelease,
|
||||
|
@ -426,20 +440,8 @@ var Navigator = React.createClass({
|
|||
this._handlers[this.state.routeStack.indexOf(route)] = handler;
|
||||
},
|
||||
|
||||
_configureSpring: function(animationConfig) {
|
||||
var config = this.spring.getSpringConfig();
|
||||
config.friction = animationConfig.springFriction;
|
||||
config.tension = animationConfig.springTension;
|
||||
},
|
||||
|
||||
componentDidMount: function() {
|
||||
this.springSystem = new rebound.SpringSystem();
|
||||
this.spring = this.springSystem.createSpring();
|
||||
this.spring.setRestSpeedThreshold(0.05);
|
||||
var animationConfig = this.state.sceneConfigStack[this.state.presentedIndex];
|
||||
animationConfig && this._configureSpring(animationConfig);
|
||||
this.spring.addListener(this);
|
||||
this.onSpringUpdate();
|
||||
this._handleSpringUpdate();
|
||||
this._emitDidFocus(this.state.routeStack[this.state.presentedIndex]);
|
||||
if (this.parentNavigator) {
|
||||
this.parentNavigator.setHandler(this._handleRequest);
|
||||
|
@ -483,98 +485,113 @@ var Navigator = React.createClass({
|
|||
updatingRangeStart: 0,
|
||||
updatingRangeLength: nextRouteStack.length,
|
||||
presentedIndex: destIndex,
|
||||
jumpToIndex: destIndex,
|
||||
toIndex: destIndex,
|
||||
fromIndex: destIndex,
|
||||
activeGesture: null,
|
||||
transitionFromIndex: null,
|
||||
transitionQueue: [],
|
||||
}, () => {
|
||||
this.onSpringUpdate();
|
||||
this._handleSpringUpdate();
|
||||
});
|
||||
},
|
||||
|
||||
_transitionTo: function(destIndex, velocity, jumpSpringTo, cb) {
|
||||
if (destIndex === this.state.presentedIndex) {
|
||||
return;
|
||||
}
|
||||
if (this.state.transitionFromIndex !== null) {
|
||||
this.state.transitionQueue.push({
|
||||
destIndex,
|
||||
velocity,
|
||||
cb,
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.state.transitionFromIndex = this.state.presentedIndex;
|
||||
this.state.presentedIndex = destIndex;
|
||||
this.state.transitionCb = cb;
|
||||
this._onAnimationStart();
|
||||
if (AnimationsDebugModule) {
|
||||
AnimationsDebugModule.startRecordingFps();
|
||||
}
|
||||
var sceneConfig = this.state.sceneConfigStack[this.state.transitionFromIndex] ||
|
||||
this.state.sceneConfigStack[this.state.presentedIndex];
|
||||
invariant(
|
||||
sceneConfig,
|
||||
'Cannot configure scene at index ' + this.state.transitionFromIndex
|
||||
);
|
||||
if (jumpSpringTo != null) {
|
||||
this.spring.setCurrentValue(jumpSpringTo);
|
||||
}
|
||||
this.spring.setOvershootClampingEnabled(true);
|
||||
this.spring.getSpringConfig().friction = sceneConfig.springFriction;
|
||||
this.spring.getSpringConfig().tension = sceneConfig.springTension;
|
||||
this.spring.setVelocity(velocity || sceneConfig.defaultTransitionVelocity);
|
||||
this.spring.setEndValue(1);
|
||||
var willFocusRoute = this._subRouteFocus[this.state.presentedIndex] || this.state.routeStack[this.state.presentedIndex];
|
||||
this._emitWillFocus(willFocusRoute);
|
||||
},
|
||||
|
||||
/**
|
||||
* TODO: Accept callback for spring completion.
|
||||
* This happens for each frame of either a gesture or a transition. If both are
|
||||
* happening, we only set values for the transition and the gesture will catch up later
|
||||
*/
|
||||
_requestTransitionTo: function(topOfStack) {
|
||||
if (topOfStack !== this.state.presentedIndex) {
|
||||
invariant(!this.state.isAnimating, 'Cannot navigate while transitioning');
|
||||
this.state.fromIndex = this.state.presentedIndex;
|
||||
this.state.toIndex = topOfStack;
|
||||
this.spring.setOvershootClampingEnabled(false);
|
||||
if (AnimationsDebugModule) {
|
||||
AnimationsDebugModule.startRecordingFps();
|
||||
}
|
||||
this._transitionToToIndexWithVelocity(
|
||||
this.state.sceneConfigStack[this.state.fromIndex].defaultTransitionVelocity
|
||||
_handleSpringUpdate: function() {
|
||||
// Prioritize handling transition in progress over a gesture:
|
||||
if (this.state.transitionFromIndex != null) {
|
||||
this._transitionBetween(
|
||||
this.state.transitionFromIndex,
|
||||
this.state.presentedIndex,
|
||||
this.spring.getCurrentValue()
|
||||
);
|
||||
} else if (this.state.activeGesture != null) {
|
||||
this._transitionBetween(
|
||||
this.state.presentedIndex,
|
||||
this.state.presentedIndex + this._deltaForGestureAction(this.state.activeGesture),
|
||||
this.spring.getCurrentValue()
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* `onSpring*` spring delegate. Wired up via `spring.addListener(this)`
|
||||
* This happens at the end of a transition started by transitionTo
|
||||
*/
|
||||
onSpringEndStateChange: function() {
|
||||
if (!this._interactionHandle) {
|
||||
this._interactionHandle = this.createInteractionHandle();
|
||||
_completeTransition: function() {
|
||||
if (this.spring.getCurrentValue() !== 1) {
|
||||
if (this.state.pendingGestureProgress) {
|
||||
this.state.pendingGestureProgress = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
onSpringUpdate: function() {
|
||||
this._transitionBetween(
|
||||
this.state.fromIndex,
|
||||
this.state.toIndex,
|
||||
this.spring.getCurrentValue()
|
||||
);
|
||||
},
|
||||
|
||||
onSpringAtRest: function() {
|
||||
this.state.isAnimating = false;
|
||||
this._completeTransition();
|
||||
this._onAnimationEnd();
|
||||
var presentedIndex = this.state.presentedIndex;
|
||||
var didFocusRoute = this._subRouteFocus[presentedIndex] || this.state.routeStack[presentedIndex];
|
||||
this._emitDidFocus(didFocusRoute);
|
||||
if (AnimationsDebugModule) {
|
||||
AnimationsDebugModule.stopRecordingFps(Date.now());
|
||||
}
|
||||
this.state.transitionFromIndex = null;
|
||||
this.spring.setCurrentValue(0).setAtRest();
|
||||
this._hideScenes();
|
||||
if (this.state.transitionCb) {
|
||||
this.state.transitionCb();
|
||||
this.state.transitionCb = null;
|
||||
}
|
||||
if (this._interactionHandle) {
|
||||
this.clearInteractionHandle(this._interactionHandle);
|
||||
this._interactionHandle = null;
|
||||
}
|
||||
},
|
||||
|
||||
_completeTransition: function() {
|
||||
if (this.spring.getCurrentValue() === 1) {
|
||||
this._onAnimationEnd();
|
||||
var presentedIndex = this.state.toIndex;
|
||||
this.state.presentedIndex = presentedIndex;
|
||||
this.state.fromIndex = presentedIndex;
|
||||
var didFocusRoute = this._subRouteFocus[presentedIndex] || this.state.routeStack[presentedIndex];
|
||||
this._emitDidFocus(didFocusRoute);
|
||||
this._removePoppedRoutes();
|
||||
if (AnimationsDebugModule) {
|
||||
AnimationsDebugModule.stopRecordingFps(Date.now());
|
||||
}
|
||||
} else {
|
||||
this.state.fromIndex = this.state.presentedIndex;
|
||||
this.state.toIndex = this.state.presentedIndex;
|
||||
if (this.state.pendingGestureProgress) {
|
||||
this.spring.setEndValue(this.state.pendingGestureProgress);
|
||||
return;
|
||||
}
|
||||
if (this.state.transitionQueue.length) {
|
||||
var queuedTransition = this.state.transitionQueue.shift();
|
||||
this._transitionTo(
|
||||
queuedTransition.destIndex,
|
||||
queuedTransition.velocity,
|
||||
null,
|
||||
queuedTransition.cb
|
||||
);
|
||||
}
|
||||
this._hideOtherScenes(this.state.presentedIndex);
|
||||
},
|
||||
|
||||
_transitionToToIndexWithVelocity: function(v) {
|
||||
this._configureSpring(
|
||||
// For visual consistency, the from index is always used to configure the spring
|
||||
this.state.sceneConfigStack[this.state.fromIndex]
|
||||
);
|
||||
this._onAnimationStart();
|
||||
this.state.isAnimating = true;
|
||||
this.spring.setVelocity(v);
|
||||
this.spring.setEndValue(1);
|
||||
var willFocusRoute = this._subRouteFocus[this.state.toIndex] || this.state.routeStack[this.state.toIndex];
|
||||
this._emitWillFocus(willFocusRoute);
|
||||
},
|
||||
|
||||
_transitionToFromIndexWithVelocity: function(v) {
|
||||
this._configureSpring(
|
||||
this.state.sceneConfigStack[this.state.fromIndex]
|
||||
);
|
||||
this.state.isAnimating = true;
|
||||
this.spring.setVelocity(v);
|
||||
this.spring.setEndValue(0);
|
||||
},
|
||||
|
||||
_emitDidFocus: function(route) {
|
||||
|
@ -608,9 +625,11 @@ var Navigator = React.createClass({
|
|||
/**
|
||||
* Does not delete the scenes - merely hides them.
|
||||
*/
|
||||
_hideOtherScenes: function(activeIndex) {
|
||||
_hideScenes: function() {
|
||||
for (var i = 0; i < this.state.routeStack.length; i++) {
|
||||
if (i === activeIndex) {
|
||||
// This gets called when we detach a gesture, so there will not be a
|
||||
// current gesture, but there might be a transition in progress
|
||||
if (i === this.state.presentedIndex || i === this.state.transitionFromIndex) {
|
||||
continue;
|
||||
}
|
||||
var sceneRef = 'scene_' + i;
|
||||
|
@ -620,22 +639,31 @@ var Navigator = React.createClass({
|
|||
},
|
||||
|
||||
_onAnimationStart: function() {
|
||||
this._setRenderSceneToHarwareTextureAndroid(this.state.fromIndex, true);
|
||||
this._setRenderSceneToHarwareTextureAndroid(this.state.toIndex, true);
|
||||
var fromIndex = this.state.presentedIndex;
|
||||
var toIndex = this.state.presentedIndex;
|
||||
if (this.state.transitionFromIndex != null) {
|
||||
fromIndex = this.state.transitionFromIndex;
|
||||
} else if (this.state.activeGesture) {
|
||||
toIndex = this.state.presentedIndex + this._deltaForGestureAction(this.state.activeGesture);
|
||||
}
|
||||
this._setRenderSceneToHarwareTextureAndroid(fromIndex, true);
|
||||
this._setRenderSceneToHarwareTextureAndroid(toIndex, true);
|
||||
|
||||
var navBar = this._navBar;
|
||||
if (navBar && navBar.onAnimationStart) {
|
||||
navBar.onAnimationStart(this.state.fromIndex, this.state.toIndex);
|
||||
navBar.onAnimationStart(fromIndex, toIndex);
|
||||
}
|
||||
},
|
||||
|
||||
_onAnimationEnd: function() {
|
||||
this._setRenderSceneToHarwareTextureAndroid(this.state.fromIndex, false);
|
||||
this._setRenderSceneToHarwareTextureAndroid(this.state.toIndex, false);
|
||||
var max = this.state.routeStack.length - 1;
|
||||
for (var index = 0; index <= max; index++) {
|
||||
this._setRenderSceneToHarwareTextureAndroid(index, false);
|
||||
}
|
||||
|
||||
var navBar = this._navBar;
|
||||
if (navBar && navBar.onAnimationEnd) {
|
||||
navBar.onAnimationEnd(this.state.fromIndex, this.state.toIndex);
|
||||
navBar.onAnimationEnd();
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -647,16 +675,6 @@ var Navigator = React.createClass({
|
|||
viewAtIndex.setNativeProps({renderToHardwareTextureAndroid: shouldRenderToHardwareTexture});
|
||||
},
|
||||
|
||||
/**
|
||||
* Becomes the responder on touch start (capture) while animating so that it
|
||||
* blocks all touch interactions inside of it. However, this responder lock
|
||||
* means nothing more than that. We record if the sole reason for being
|
||||
* responder is to block interactions (`isResponderOnlyToBlockTouches`).
|
||||
*/
|
||||
_handleStartShouldSetPanResponderCapture: function(e, gestureState) {
|
||||
return this.state.isAnimating;
|
||||
},
|
||||
|
||||
_handleMoveShouldSetPanResponder: function(e, gestureState) {
|
||||
var currentRoute = this.state.routeStack[this.state.presentedIndex];
|
||||
var sceneConfig = this.state.sceneConfigStack[this.state.presentedIndex];
|
||||
|
@ -674,18 +692,12 @@ var Navigator = React.createClass({
|
|||
|
||||
_handlePanResponderGrant: function(e, gestureState) {
|
||||
invariant(
|
||||
this._expectingGestureGrant || this.state.isAnimating,
|
||||
this._expectingGestureGrant,
|
||||
'Responder granted unexpectedly.'
|
||||
);
|
||||
this._activeGestureAction = this._expectingGestureGrant;
|
||||
this._attachGesture(this._expectingGestureGrant);
|
||||
this._onAnimationStart();
|
||||
this._expectingGestureGrant = null;
|
||||
this.state.isResponderOnlyToBlockTouches = this.state.isAnimating;
|
||||
if (!this.state.isAnimating) {
|
||||
this.state.fromIndex = this.state.presentedIndex;
|
||||
var gestureSceneDelta = this._deltaForGestureAction(this._activeGestureAction);
|
||||
this.state.toIndex = this.state.presentedIndex + gestureSceneDelta;
|
||||
this._onAnimationStart();
|
||||
}
|
||||
},
|
||||
|
||||
_deltaForGestureAction: function(gestureAction) {
|
||||
|
@ -703,13 +715,13 @@ var Navigator = React.createClass({
|
|||
|
||||
_handlePanResponderRelease: function(e, gestureState) {
|
||||
var sceneConfig = this.state.sceneConfigStack[this.state.presentedIndex];
|
||||
var releaseGestureAction = this._activeGestureAction;
|
||||
this._activeGestureAction = null;
|
||||
if (this.state.isResponderOnlyToBlockTouches) {
|
||||
this.state.isResponderOnlyToBlockTouches = false;
|
||||
var releaseGestureAction = this.state.activeGesture;
|
||||
if (!releaseGestureAction) {
|
||||
// The gesture may have been detached while responder, so there is no action here
|
||||
return;
|
||||
}
|
||||
var releaseGesture = sceneConfig.gestures[releaseGestureAction];
|
||||
var destIndex = this.state.presentedIndex + this._deltaForGestureAction(this.state.activeGesture);
|
||||
if (this.spring.getCurrentValue() === 0) {
|
||||
// The spring is at zero, so the gesture is already complete
|
||||
this.spring.setCurrentValue(0).setAtRest();
|
||||
|
@ -732,38 +744,86 @@ var Navigator = React.createClass({
|
|||
var hasGesturedEnoughToComplete = gestureDistance > releaseGesture.fullDistance * releaseGesture.stillCompletionRatio;
|
||||
transitionVelocity = hasGesturedEnoughToComplete ? releaseGesture.snapVelocity : -releaseGesture.snapVelocity;
|
||||
}
|
||||
this.spring.setOvershootClampingEnabled(true);
|
||||
if (transitionVelocity < 0 || this._doesGestureOverswipe(releaseGestureAction)) {
|
||||
this._transitionToFromIndexWithVelocity(transitionVelocity);
|
||||
// This gesture is to an overswiped region or does not have enough velocity to complete
|
||||
// If we are currently mid-transition, then this gesture was a pending gesture. Because this gesture takes no action, we can stop here
|
||||
if (this.state.transitionFromIndex == null) {
|
||||
// There is no current transition, so we need to transition back to the presented index
|
||||
var transitionBackToPresentedIndex = this.state.presentedIndex;
|
||||
// slight hack: change the presented index for a moment in order to transitionTo correctly
|
||||
this.state.presentedIndex = destIndex;
|
||||
this._transitionTo(
|
||||
transitionBackToPresentedIndex,
|
||||
- transitionVelocity,
|
||||
1 - this.spring.getCurrentValue()
|
||||
);
|
||||
}
|
||||
} else {
|
||||
this._transitionToToIndexWithVelocity(transitionVelocity);
|
||||
// The gesture has enough velocity to complete, so we transition to the gesture's destination
|
||||
this._transitionTo(destIndex, transitionVelocity);
|
||||
}
|
||||
this._detachGesture();
|
||||
},
|
||||
|
||||
_handlePanResponderTerminate: function(e, gestureState) {
|
||||
this._activeGestureAction = null;
|
||||
this.state.isResponderOnlyToBlockTouches = false;
|
||||
this._transitionToFromIndexWithVelocity(0);
|
||||
var destIndex = this.state.presentedIndex + this._deltaForGestureAction(this.state.activeGesture);
|
||||
this._detachGesture();
|
||||
var transitionBackToPresentedIndex = this.state.presentedIndex;
|
||||
// slight hack: change the presented index for a moment in order to transitionTo correctly
|
||||
this.state.presentedIndex = destIndex;
|
||||
this._transitionTo(
|
||||
transitionBackToPresentedIndex,
|
||||
null,
|
||||
1 - this.spring.getCurrentValue()
|
||||
);
|
||||
},
|
||||
|
||||
_attachGesture: function(gestureId) {
|
||||
this.state.activeGesture = gestureId;
|
||||
},
|
||||
|
||||
_detachGesture: function() {
|
||||
this.state.activeGesture = null;
|
||||
this.state.pendingGestureProgress = null;
|
||||
this._hideScenes();
|
||||
},
|
||||
|
||||
_handlePanResponderMove: function(e, gestureState) {
|
||||
if (!this.state.isResponderOnlyToBlockTouches) {
|
||||
var sceneConfig = this.state.sceneConfigStack[this.state.presentedIndex];
|
||||
var gesture = sceneConfig.gestures[this._activeGestureAction];
|
||||
var isTravelVertical = gesture.direction === 'top-to-bottom' || gesture.direction === 'bottom-to-top';
|
||||
var isTravelInverted = gesture.direction === 'right-to-left' || gesture.direction === 'bottom-to-top';
|
||||
var distance = isTravelVertical ? gestureState.dy : gestureState.dx;
|
||||
distance = isTravelInverted ? - distance : distance;
|
||||
var gestureDetectMovement = gesture.gestureDetectMovement;
|
||||
var nextProgress = (distance - gestureDetectMovement) /
|
||||
(gesture.fullDistance - gestureDetectMovement);
|
||||
if (this._doesGestureOverswipe(this._activeGestureAction)) {
|
||||
var frictionConstant = gesture.overswipe.frictionConstant;
|
||||
var frictionByDistance = gesture.overswipe.frictionByDistance;
|
||||
var frictionRatio = 1 / ((frictionConstant) + (Math.abs(nextProgress) * frictionByDistance));
|
||||
nextProgress *= frictionRatio;
|
||||
}
|
||||
this.spring.setCurrentValue(clamp(0, nextProgress, 1));
|
||||
var sceneConfig = this.state.sceneConfigStack[this.state.presentedIndex];
|
||||
if (this.state.activeGesture) {
|
||||
var gesture = sceneConfig.gestures[this.state.activeGesture];
|
||||
return this._moveAttachedGesture(gesture, gestureState);
|
||||
}
|
||||
var matchedGesture = this._matchGestureAction(sceneConfig.gestures, gestureState);
|
||||
if (matchedGesture) {
|
||||
this._attachGesture(matchedGesture);
|
||||
}
|
||||
},
|
||||
|
||||
_moveAttachedGesture: function(gesture, gestureState) {
|
||||
var isTravelVertical = gesture.direction === 'top-to-bottom' || gesture.direction === 'bottom-to-top';
|
||||
var isTravelInverted = gesture.direction === 'right-to-left' || gesture.direction === 'bottom-to-top';
|
||||
var distance = isTravelVertical ? gestureState.dy : gestureState.dx;
|
||||
distance = isTravelInverted ? - distance : distance;
|
||||
var gestureDetectMovement = gesture.gestureDetectMovement;
|
||||
var nextProgress = (distance - gestureDetectMovement) /
|
||||
(gesture.fullDistance - gestureDetectMovement);
|
||||
if (nextProgress < 0 && gesture.isDetachable) {
|
||||
this._detachGesture();
|
||||
}
|
||||
if (this._doesGestureOverswipe(this.state.activeGesture)) {
|
||||
var frictionConstant = gesture.overswipe.frictionConstant;
|
||||
var frictionByDistance = gesture.overswipe.frictionByDistance;
|
||||
var frictionRatio = 1 / ((frictionConstant) + (Math.abs(nextProgress) * frictionByDistance));
|
||||
nextProgress *= frictionRatio;
|
||||
}
|
||||
nextProgress = clamp(0, nextProgress, 1);
|
||||
if (this.state.transitionFromIndex != null) {
|
||||
this.state.pendingGestureProgress = nextProgress;
|
||||
} else if (this.state.pendingGestureProgress) {
|
||||
this.spring.setEndValue(nextProgress);
|
||||
} else {
|
||||
this.spring.setCurrentValue(nextProgress);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -771,9 +831,6 @@ var Navigator = React.createClass({
|
|||
if (!gestures) {
|
||||
return null;
|
||||
}
|
||||
if (this.state.isResponderOnlyToBlockTouches || this.state.isAnimating) {
|
||||
return null;
|
||||
}
|
||||
var matchedGesture = null;
|
||||
GESTURE_ACTIONS.some((gestureName) => {
|
||||
var gesture = gestures[gestureName];
|
||||
|
@ -814,7 +871,7 @@ var Navigator = React.createClass({
|
|||
return;
|
||||
}
|
||||
// Use toIndex animation when we move forwards. Use fromIndex when we move back
|
||||
var sceneConfigIndex = this.state.presentedIndex < toIndex ? toIndex : fromIndex;
|
||||
var sceneConfigIndex = fromIndex < toIndex ? toIndex : fromIndex;
|
||||
var sceneConfig = this.state.sceneConfigStack[sceneConfigIndex];
|
||||
// this happens for overswiping when there is no scene at toIndex
|
||||
if (!sceneConfig) {
|
||||
|
@ -849,10 +906,6 @@ var Navigator = React.createClass({
|
|||
this.state.updatingRangeLength = this.state.routeStack.length;
|
||||
},
|
||||
|
||||
_canNavigate: function() {
|
||||
return !this.state.isAnimating;
|
||||
},
|
||||
|
||||
_getDestIndexWithinBounds: function(n) {
|
||||
var currentIndex = this.state.presentedIndex;
|
||||
var destIndex = currentIndex + n;
|
||||
|
@ -870,17 +923,13 @@ var Navigator = React.createClass({
|
|||
|
||||
_jumpN: function(n) {
|
||||
var destIndex = this._getDestIndexWithinBounds(n);
|
||||
if (!this._canNavigate()) {
|
||||
return; // It's busy animating or transitioning.
|
||||
}
|
||||
var requestTransitionAndResetUpdatingRange = () => {
|
||||
this._requestTransitionTo(destIndex);
|
||||
this._transitionTo(destIndex);
|
||||
this._resetUpdatingRange();
|
||||
};
|
||||
this.setState({
|
||||
updatingRangeStart: destIndex,
|
||||
updatingRangeLength: 1,
|
||||
toIndex: destIndex,
|
||||
}, requestTransitionAndResetUpdatingRange);
|
||||
},
|
||||
|
||||
|
@ -903,9 +952,6 @@ var Navigator = React.createClass({
|
|||
|
||||
push: function(route) {
|
||||
invariant(!!route, 'Must supply route to push');
|
||||
if (!this._canNavigate()) {
|
||||
return; // It's busy animating or transitioning.
|
||||
}
|
||||
var activeLength = this.state.presentedIndex + 1;
|
||||
var activeStack = this.state.routeStack.slice(0, activeLength);
|
||||
var activeIDStack = this.state.idStack.slice(0, activeLength);
|
||||
|
@ -916,34 +962,34 @@ var Navigator = React.createClass({
|
|||
this.props.configureScene(route),
|
||||
]);
|
||||
var requestTransitionAndResetUpdatingRange = () => {
|
||||
this._requestTransitionTo(nextStack.length - 1);
|
||||
this._transitionTo(nextStack.length - 1);
|
||||
this._resetUpdatingRange();
|
||||
};
|
||||
var navigationState = {
|
||||
toRoute: route,
|
||||
fromRoute: this.state.routeStack[this.state.routeStack.length - 1],
|
||||
};
|
||||
this.setState({
|
||||
idStack: nextIDStack,
|
||||
routeStack: nextStack,
|
||||
sceneConfigStack: nextAnimationConfigStack,
|
||||
jumpToIndex: nextStack.length - 1,
|
||||
updatingRangeStart: nextStack.length - 1,
|
||||
updatingRangeLength: 1,
|
||||
}, requestTransitionAndResetUpdatingRange);
|
||||
},
|
||||
|
||||
_popN: function(n) {
|
||||
if (n === 0 || !this._canNavigate()) {
|
||||
if (n === 0) {
|
||||
return;
|
||||
}
|
||||
invariant(
|
||||
this.state.presentedIndex - n >= 0,
|
||||
'Cannot pop below zero'
|
||||
);
|
||||
this.state.jumpToIndex = this.state.presentedIndex - n;
|
||||
this._requestTransitionTo(
|
||||
this.state.presentedIndex - n
|
||||
var popIndex = this.state.presentedIndex - n;
|
||||
this._transitionTo(
|
||||
popIndex,
|
||||
null, // default velocity
|
||||
null, // no spring jumping
|
||||
() => {
|
||||
this._cleanScenesPastIndex(popIndex);
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
|
@ -957,7 +1003,7 @@ var Navigator = React.createClass({
|
|||
* `index` specifies the route in the stack that should be replaced.
|
||||
* If it's negative, it counts from the back.
|
||||
*/
|
||||
replaceAtIndex: function(route, index) {
|
||||
replaceAtIndex: function(route, index, cb) {
|
||||
invariant(!!route, 'Must supply route to replace');
|
||||
if (index < 0) {
|
||||
index += this.state.routeStack.length;
|
||||
|
@ -988,6 +1034,7 @@ var Navigator = React.createClass({
|
|||
this._emitWillFocus(route);
|
||||
this._emitDidFocus(route);
|
||||
}
|
||||
cb && cb();
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -1024,7 +1071,7 @@ var Navigator = React.createClass({
|
|||
},
|
||||
|
||||
replacePreviousAndPop: function(route) {
|
||||
if (this.state.routeStack.length < 2 || !this._canNavigate()) {
|
||||
if (this.state.routeStack.length < 2) {
|
||||
return;
|
||||
}
|
||||
this.replacePrevious(route);
|
||||
|
@ -1033,10 +1080,9 @@ var Navigator = React.createClass({
|
|||
|
||||
resetTo: function(route) {
|
||||
invariant(!!route, 'Must supply route to push');
|
||||
if (this._canNavigate()) {
|
||||
this.replaceAtIndex(route, 0);
|
||||
this.replaceAtIndex(route, 0, () => {
|
||||
this.popToRoute(route);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
getCurrentRoutes: function() {
|
||||
|
@ -1052,8 +1098,8 @@ var Navigator = React.createClass({
|
|||
this.props.onItemRef && this.props.onItemRef(ref, itemIndex);
|
||||
},
|
||||
|
||||
_removePoppedRoutes: function() {
|
||||
var newStackLength = this.state.jumpToIndex + 1;
|
||||
_cleanScenesPastIndex: function(index) {
|
||||
var newStackLength = index + 1;
|
||||
// Remove any unneeded rendered routes.
|
||||
if (newStackLength < this.state.routeStack.length) {
|
||||
var updatingRangeStart = newStackLength; // One past the top
|
||||
|
|
|
@ -33,6 +33,8 @@ var StaticContainer = require('StaticContainer.react');
|
|||
var StyleSheet = require('StyleSheet');
|
||||
var View = require('View');
|
||||
|
||||
var invariant = require('invariant');
|
||||
|
||||
var Interpolators = NavigatorBreadcrumbNavigationBarStyles.Interpolators;
|
||||
var PropTypes = React.PropTypes;
|
||||
|
||||
|
@ -99,6 +101,10 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
|
|||
var oldDistToCenter = index - fromIndex;
|
||||
var newDistToCenter = index - toIndex;
|
||||
var interpolate;
|
||||
invariant(
|
||||
Interpolators[index],
|
||||
'Cannot find breadcrumb interpolators for ' + index
|
||||
);
|
||||
if (oldDistToCenter > 0 && newDistToCenter === 0 ||
|
||||
newDistToCenter > 0 && oldDistToCenter === 0) {
|
||||
interpolate = Interpolators[index].RightToCenter;
|
||||
|
@ -146,10 +152,9 @@ var NavigatorBreadcrumbNavigationBar = React.createClass({
|
|||
}
|
||||
},
|
||||
|
||||
onAnimationEnd: function(fromIndex, toIndex) {
|
||||
var max = Math.max(fromIndex, toIndex);
|
||||
var min = Math.min(fromIndex, toIndex);
|
||||
for (var index = min; index <= max; index++) {
|
||||
onAnimationEnd: function() {
|
||||
var max = this.props.navState.routeStack.length - 1;
|
||||
for (var index = 0; index <= max; index++) {
|
||||
this._setRenderViewsToHardwareTextureAndroid(index, false);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -101,6 +101,30 @@ var FadeToTheLeft = {
|
|||
},
|
||||
};
|
||||
|
||||
var FadeIn = {
|
||||
opacity: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
min: 0.5,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: false,
|
||||
round: 100,
|
||||
},
|
||||
};
|
||||
|
||||
var FadeOut = {
|
||||
opacity: {
|
||||
from: 1,
|
||||
to: 0,
|
||||
min: 0,
|
||||
max: 0.5,
|
||||
type: 'linear',
|
||||
extrapolate: false,
|
||||
round: 100,
|
||||
},
|
||||
};
|
||||
|
||||
var ToTheLeft = {
|
||||
transformTranslate: {
|
||||
from: {x: 0, y: 0, z: 0},
|
||||
|
@ -115,8 +139,17 @@ var ToTheLeft = {
|
|||
value: 1.0,
|
||||
type: 'constant',
|
||||
},
|
||||
};
|
||||
|
||||
translateX: {
|
||||
from: 0,
|
||||
to: -Dimensions.get('window').width,
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true,
|
||||
round: PixelRatio.get(),
|
||||
},
|
||||
};
|
||||
|
||||
var FromTheRight = {
|
||||
opacity: {
|
||||
|
@ -236,6 +269,43 @@ var FromTheFront = {
|
|||
},
|
||||
};
|
||||
|
||||
var ToTheBackAndroid = {
|
||||
opacity: {
|
||||
value: 1,
|
||||
type: 'constant',
|
||||
},
|
||||
};
|
||||
|
||||
var FromTheFrontAndroid = {
|
||||
opacity: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: false,
|
||||
round: 100,
|
||||
},
|
||||
transformTranslate: {
|
||||
from: {x: 0, y: 50, z: 0},
|
||||
to: {x: 0, y: 0, z: 0},
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true,
|
||||
round: PixelRatio.get(),
|
||||
},
|
||||
translateY: {
|
||||
from: 50,
|
||||
to: 0,
|
||||
min: 0,
|
||||
max: 1,
|
||||
type: 'linear',
|
||||
extrapolate: true,
|
||||
round: PixelRatio.get(),
|
||||
},
|
||||
};
|
||||
|
||||
var BaseOverswipeConfig = {
|
||||
frictionConstant: 1,
|
||||
frictionByDistance: 1.5,
|
||||
|
@ -243,6 +313,9 @@ var BaseOverswipeConfig = {
|
|||
|
||||
var BaseLeftToRightGesture = {
|
||||
|
||||
// If the gesture can end and restart during one continuous touch
|
||||
isDetachable: false,
|
||||
|
||||
// How far the swipe must drag to start transitioning
|
||||
gestureDetectMovement: 2,
|
||||
|
||||
|
@ -316,6 +389,22 @@ var NavigatorSceneConfigs = {
|
|||
out: buildStyleInterpolator(ToTheBack),
|
||||
},
|
||||
},
|
||||
FloatFromBottomAndroid: {
|
||||
...BaseConfig,
|
||||
gestures: null,
|
||||
animationInterpolators: {
|
||||
into: buildStyleInterpolator(FromTheFrontAndroid),
|
||||
out: buildStyleInterpolator(ToTheBackAndroid),
|
||||
},
|
||||
},
|
||||
FadeAndroid: {
|
||||
...BaseConfig,
|
||||
gestures: null,
|
||||
animationInterpolators: {
|
||||
into: buildStyleInterpolator(FadeIn),
|
||||
out: buildStyleInterpolator(FadeOut),
|
||||
},
|
||||
},
|
||||
HorizontalSwipeJump: {
|
||||
...BaseConfig,
|
||||
gestures: {
|
||||
|
@ -323,11 +412,13 @@ var NavigatorSceneConfigs = {
|
|||
...BaseLeftToRightGesture,
|
||||
overswipe: BaseOverswipeConfig,
|
||||
edgeHitWidth: null,
|
||||
isDetachable: true,
|
||||
},
|
||||
jumpForward: {
|
||||
...BaseRightToLeftGesture,
|
||||
overswipe: BaseOverswipeConfig,
|
||||
edgeHitWidth: null,
|
||||
isDetachable: true,
|
||||
},
|
||||
},
|
||||
animationInterpolators: {
|
||||
|
|
|
@ -22,6 +22,12 @@ var subscriptions = [];
|
|||
|
||||
var updatesEnabled = false;
|
||||
|
||||
type GeoOptions = {
|
||||
timeout: number;
|
||||
maximumAge: number;
|
||||
enableHighAccuracy: bool;
|
||||
}
|
||||
|
||||
/**
|
||||
* You need to include the `NSLocationWhenInUseUsageDescription` key
|
||||
* in Info.plist to enable geolocation. Geolocation is enabled by default
|
||||
|
@ -32,10 +38,14 @@ var updatesEnabled = false;
|
|||
*/
|
||||
var Geolocation = {
|
||||
|
||||
/*
|
||||
* Invokes the success callback once with the latest location info. Supported
|
||||
* options: timeout (ms), maximumAge (ms), enableHighAccuracy (bool)
|
||||
*/
|
||||
getCurrentPosition: function(
|
||||
geo_success: Function,
|
||||
geo_error?: Function,
|
||||
geo_options?: Object
|
||||
geo_options?: GeoOptions
|
||||
) {
|
||||
invariant(
|
||||
typeof geo_success === 'function',
|
||||
|
@ -48,7 +58,11 @@ var Geolocation = {
|
|||
);
|
||||
},
|
||||
|
||||
watchPosition: function(success: Function, error?: Function, options?: Object): number {
|
||||
/*
|
||||
* Invokes the success callback whenever the location changes. Supported
|
||||
* options: timeout (ms), maximumAge (ms), enableHighAccuracy (bool)
|
||||
*/
|
||||
watchPosition: function(success: Function, error?: Function, options?: GeoOptions): number {
|
||||
if (!updatesEnabled) {
|
||||
RCTLocationObserver.startObserving(options || {});
|
||||
updatesEnabled = true;
|
||||
|
|
|
@ -163,12 +163,12 @@ RCT_EXPORT_MODULE()
|
|||
|
||||
#pragma mark - Public API
|
||||
|
||||
RCT_EXPORT_METHOD(startObserving:(NSDictionary *)optionsJSON)
|
||||
RCT_EXPORT_METHOD(startObserving:(RCTLocationOptions)options)
|
||||
{
|
||||
[self checkLocationConfig];
|
||||
|
||||
// Select best options
|
||||
_observerOptions = [RCTConvert RCTLocationOptions:optionsJSON];
|
||||
_observerOptions = options;
|
||||
for (RCTLocationRequest *request in _pendingRequests) {
|
||||
_observerOptions.accuracy = MIN(_observerOptions.accuracy, request.options.accuracy);
|
||||
}
|
||||
|
@ -189,7 +189,7 @@ RCT_EXPORT_METHOD(stopObserving)
|
|||
}
|
||||
}
|
||||
|
||||
RCT_EXPORT_METHOD(getCurrentPosition:(NSDictionary *)optionsJSON
|
||||
RCT_EXPORT_METHOD(getCurrentPosition:(RCTLocationOptions)options
|
||||
withSuccessCallback:(RCTResponseSenderBlock)successBlock
|
||||
errorCallback:(RCTResponseSenderBlock)errorBlock)
|
||||
{
|
||||
|
@ -219,7 +219,6 @@ RCT_EXPORT_METHOD(getCurrentPosition:(NSDictionary *)optionsJSON
|
|||
}
|
||||
|
||||
// Check if previous recorded location exists and is good enough
|
||||
RCTLocationOptions options = [RCTConvert RCTLocationOptions:optionsJSON];
|
||||
if (_lastLocationEvent &&
|
||||
CFAbsoluteTimeGetCurrent() - [RCTConvert NSTimeInterval:_lastLocationEvent[@"timestamp"]] < options.maximumAge &&
|
||||
[_lastLocationEvent[@"coords"][@"accuracy"] doubleValue] >= options.accuracy) {
|
||||
|
|
|
@ -31,7 +31,7 @@ var verifyPropTypes = require('verifyPropTypes');
|
|||
var warning = require('warning');
|
||||
|
||||
/**
|
||||
* A react component for displaying different types of images,
|
||||
* A React component for displaying different types of images,
|
||||
* including network images, static resources, temporary local images, and
|
||||
* images from local disk, such as the camera roll.
|
||||
*
|
||||
|
|
|
@ -32,6 +32,12 @@ describe('resolveAssetSource', () => {
|
|||
expect(resolveAssetSource(source2)).toBe(source2);
|
||||
});
|
||||
|
||||
it('ignores any weird data', () => {
|
||||
expect(resolveAssetSource(null)).toBe(null);
|
||||
expect(resolveAssetSource(42)).toBe(null);
|
||||
expect(resolveAssetSource('nonsense')).toBe(null);
|
||||
});
|
||||
|
||||
describe('bundle was loaded from network', () => {
|
||||
beforeEach(() => {
|
||||
SourceCode.scriptURL = 'http://10.0.0.1:8081/main.bundle';
|
||||
|
|
|
@ -43,10 +43,11 @@ function pickScale(scales, deviceScale) {
|
|||
return scales[scales.length - 1] || 1;
|
||||
}
|
||||
|
||||
// TODO(frantic):
|
||||
// * Pick best scale and append @Nx to file path
|
||||
// * We are currently using httpServerLocation for both http and in-app bundle
|
||||
function resolveAssetSource(source) {
|
||||
if (!source || typeof source !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!source.__packager_asset) {
|
||||
return source;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ var RCTExceptionsManager = require('NativeModules').ExceptionsManager;
|
|||
|
||||
var loadSourceMap = require('loadSourceMap');
|
||||
var parseErrorStack = require('parseErrorStack');
|
||||
var stringifySafe = require('stringifySafe');
|
||||
|
||||
var sourceMapPromise;
|
||||
|
||||
|
@ -25,17 +26,11 @@ type Exception = {
|
|||
message: string;
|
||||
}
|
||||
|
||||
function handleException(e: Exception) {
|
||||
var stack = parseErrorStack(e);
|
||||
console.error(
|
||||
'Err0r: ' +
|
||||
'\n stack: \n' + stackToString(stack) +
|
||||
'\n URL: ' + e.sourceURL +
|
||||
'\n line: ' + e.line +
|
||||
'\n message: ' + e.message
|
||||
);
|
||||
|
||||
function reportException(e: Exception, stack?: any) {
|
||||
if (RCTExceptionsManager) {
|
||||
if (!stack) {
|
||||
stack = parseErrorStack(e);
|
||||
}
|
||||
RCTExceptionsManager.reportUnhandledException(e.message, stack);
|
||||
if (__DEV__) {
|
||||
(sourceMapPromise = sourceMapPromise || loadSourceMap())
|
||||
|
@ -50,6 +45,47 @@ function handleException(e: Exception) {
|
|||
}
|
||||
}
|
||||
|
||||
function handleException(e: Exception) {
|
||||
var stack = parseErrorStack(e);
|
||||
var msg =
|
||||
'Error: ' + e.message +
|
||||
'\n stack: \n' + stackToString(stack) +
|
||||
'\n URL: ' + e.sourceURL +
|
||||
'\n line: ' + e.line +
|
||||
'\n message: ' + e.message;
|
||||
if (console.errorOriginal) {
|
||||
console.errorOriginal(msg);
|
||||
} else {
|
||||
console.error(msg);
|
||||
}
|
||||
reportException(e, stack);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a redbox with stacktrace for all console.error messages. Disable by
|
||||
* setting `console.reportErrorsAsExceptions = false;` in your app.
|
||||
*/
|
||||
function installConsoleErrorReporter() {
|
||||
if (console.reportException) {
|
||||
return; // already installed
|
||||
}
|
||||
console.reportException = reportException;
|
||||
console.errorOriginal = console.error.bind(console);
|
||||
console.error = function reactConsoleError() {
|
||||
console.errorOriginal.apply(null, arguments);
|
||||
if (!console.reportErrorsAsExceptions) {
|
||||
return;
|
||||
}
|
||||
var str = Array.prototype.map.call(arguments, stringifySafe).join(', ');
|
||||
var error: any = new Error('console.error: ' + str);
|
||||
error.framesToPop = 1;
|
||||
reportException(error);
|
||||
};
|
||||
if (console.reportErrorsAsExceptions === undefined) {
|
||||
console.reportErrorsAsExceptions = true; // Individual apps can disable this
|
||||
}
|
||||
}
|
||||
|
||||
function stackToString(stack) {
|
||||
var maxLength = Math.max.apply(null, stack.map(frame => frame.methodName.length));
|
||||
return stack.map(frame => stackFrameToString(frame, maxLength)).join('\n');
|
||||
|
@ -71,4 +107,4 @@ function fillSpaces(n) {
|
|||
return new Array(n + 1).join(' ');
|
||||
}
|
||||
|
||||
module.exports = { handleException };
|
||||
module.exports = { handleException, installConsoleErrorReporter };
|
||||
|
|
|
@ -61,7 +61,7 @@ RCT_EXPORT_METHOD(queryData:(NSString *)queryType
|
|||
responseJSON = @{
|
||||
@"status": @0,
|
||||
@"responseHeaders": @{},
|
||||
@"responseText": [connectionError localizedDescription]
|
||||
@"responseText": [connectionError localizedDescription] ?: [NSNull null]
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ var ReactInstanceMap = require('ReactInstanceMap');
|
|||
*
|
||||
* `mountImage`: A way to represent the potential to create lower level
|
||||
* resources whos `nodeHandle` can be discovered immediately by knowing the
|
||||
* `rootNodeID`. Today's web react represents this with `innerHTML` annotated
|
||||
* `rootNodeID`. Today's web React represents this with `innerHTML` annotated
|
||||
* with DOM ids that match the `rootNodeID`.
|
||||
*
|
||||
* Opaque name TodaysWebReact FutureWebWorkerReact ReactNative
|
||||
|
|
|
@ -191,7 +191,7 @@ var ReactIOSMount = {
|
|||
* that has been rendered and unmounting it. There should just be one child
|
||||
* component at this time.
|
||||
*/
|
||||
unmountComponentAtNode: function(containerTag: number): bool {
|
||||
unmountComponentAtNode: function(containerTag: number): boolean {
|
||||
if (!ReactIOSTagHandles.reactTagIsNativeTopRootID(containerTag)) {
|
||||
console.error('You cannot render into anything but a top root');
|
||||
return false;
|
||||
|
|
|
@ -0,0 +1,398 @@
|
|||
/**
|
||||
* 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 WarningBox
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
var AsyncStorage = require('AsyncStorage');
|
||||
var EventEmitter = require('EventEmitter');
|
||||
var Map = require('Map');
|
||||
var PanResponder = require('PanResponder');
|
||||
var React = require('React');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var Text = require('Text');
|
||||
var TouchableOpacity = require('TouchableOpacity');
|
||||
var View = require('View');
|
||||
|
||||
var invariant = require('invariant');
|
||||
var rebound = require('rebound');
|
||||
var stringifySafe = require('stringifySafe');
|
||||
|
||||
var SCREEN_WIDTH = require('Dimensions').get('window').width;
|
||||
var IGNORED_WARNINGS_KEY = '__DEV_WARNINGS_IGNORED';
|
||||
|
||||
var consoleWarn = console.warn.bind(console);
|
||||
|
||||
var warningCounts = new Map();
|
||||
var ignoredWarnings: Array<string> = [];
|
||||
var totalWarningCount = 0;
|
||||
var warningCountEvents = new EventEmitter();
|
||||
|
||||
/**
|
||||
* WarningBox renders warnings on top of the app being developed. Warnings help
|
||||
* guard against subtle yet significant issues that can impact the quality of
|
||||
* your application, such as accessibility and memory leaks. This "in your
|
||||
* face" style of warning allows developers to notice and correct these issues
|
||||
* as quickly as possible.
|
||||
*
|
||||
* The warning box is currently opt-in. Set the following flag to enable it:
|
||||
*
|
||||
* `console.yellowBoxEnabled = true;`
|
||||
*
|
||||
* If "ignore" is tapped on a warning, the WarningBox will record that warning
|
||||
* and will not display it again. This is useful for hiding errors that already
|
||||
* exist or have been introduced elsewhere. To re-enable all of the errors, set
|
||||
* the following:
|
||||
*
|
||||
* `console.yellowBoxResetIgnored = true;`
|
||||
*
|
||||
* This can also be set permanently, and ignore will only silence the warnings
|
||||
* until the next refresh.
|
||||
*/
|
||||
|
||||
if (__DEV__) {
|
||||
console.warn = function() {
|
||||
consoleWarn.apply(null, arguments);
|
||||
if (!console.yellowBoxEnabled) {
|
||||
return;
|
||||
}
|
||||
var warning = Array.prototype.map.call(arguments, stringifySafe).join(' ');
|
||||
if (!console.yellowBoxResetIgnored &&
|
||||
ignoredWarnings.indexOf(warning) !== -1) {
|
||||
return;
|
||||
}
|
||||
var count = warningCounts.has(warning) ? warningCounts.get(warning) + 1 : 1;
|
||||
warningCounts.set(warning, count);
|
||||
totalWarningCount += 1;
|
||||
warningCountEvents.emit('count', totalWarningCount);
|
||||
};
|
||||
}
|
||||
|
||||
function saveIgnoredWarnings() {
|
||||
AsyncStorage.setItem(
|
||||
IGNORED_WARNINGS_KEY,
|
||||
JSON.stringify(ignoredWarnings),
|
||||
function(err) {
|
||||
if (err) {
|
||||
console.warn('Could not save ignored warnings.', err);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
AsyncStorage.getItem(IGNORED_WARNINGS_KEY, function(err, data) {
|
||||
if (!err && data && !console.yellowBoxResetIgnored) {
|
||||
ignoredWarnings = JSON.parse(data);
|
||||
}
|
||||
});
|
||||
|
||||
var WarningRow = React.createClass({
|
||||
componentWillMount: function() {
|
||||
this.springSystem = new rebound.SpringSystem();
|
||||
this.dismissalSpring = this.springSystem.createSpring();
|
||||
this.dismissalSpring.setRestSpeedThreshold(0.05);
|
||||
this.dismissalSpring.setCurrentValue(0);
|
||||
this.dismissalSpring.addListener({
|
||||
onSpringUpdate: () => {
|
||||
var val = this.dismissalSpring.getCurrentValue();
|
||||
this.text && this.text.setNativeProps({
|
||||
left: SCREEN_WIDTH * val,
|
||||
});
|
||||
this.container && this.container.setNativeProps({
|
||||
opacity: 1 - val,
|
||||
});
|
||||
this.closeButton && this.closeButton.setNativeProps({
|
||||
opacity: 1 - (val * 5),
|
||||
});
|
||||
},
|
||||
onSpringAtRest: () => {
|
||||
if (this.dismissalSpring.getCurrentValue()) {
|
||||
this.collapseSpring.setEndValue(1);
|
||||
}
|
||||
},
|
||||
});
|
||||
this.collapseSpring = this.springSystem.createSpring();
|
||||
this.collapseSpring.setRestSpeedThreshold(0.05);
|
||||
this.collapseSpring.setCurrentValue(0);
|
||||
this.collapseSpring.getSpringConfig().friction = 20;
|
||||
this.collapseSpring.getSpringConfig().tension = 200;
|
||||
this.collapseSpring.addListener({
|
||||
onSpringUpdate: () => {
|
||||
var val = this.collapseSpring.getCurrentValue();
|
||||
this.container && this.container.setNativeProps({
|
||||
height: Math.abs(46 - (val * 46)),
|
||||
});
|
||||
},
|
||||
onSpringAtRest: () => {
|
||||
this.props.onDismissed();
|
||||
},
|
||||
});
|
||||
this.panGesture = PanResponder.create({
|
||||
onStartShouldSetPanResponder: () => {
|
||||
return !! this.dismissalSpring.getCurrentValue();
|
||||
},
|
||||
onMoveShouldSetPanResponder: () => true,
|
||||
onPanResponderGrant: () => {
|
||||
this.isResponderOnlyToBlockTouches =
|
||||
!! this.dismissalSpring.getCurrentValue();
|
||||
},
|
||||
onPanResponderMove: (e, gestureState) => {
|
||||
if (this.isResponderOnlyToBlockTouches) {
|
||||
return;
|
||||
}
|
||||
this.dismissalSpring.setCurrentValue(gestureState.dx / SCREEN_WIDTH);
|
||||
},
|
||||
onPanResponderRelease: (e, gestureState) => {
|
||||
if (this.isResponderOnlyToBlockTouches) {
|
||||
return;
|
||||
}
|
||||
var gestureCompletion = gestureState.dx / SCREEN_WIDTH;
|
||||
var doesGestureRelease = (gestureState.vx + gestureCompletion) > 0.5;
|
||||
this.dismissalSpring.setEndValue(doesGestureRelease ? 1 : 0);
|
||||
}
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
var countText;
|
||||
if (warningCounts.get(this.props.warning) > 1) {
|
||||
countText = (
|
||||
<Text style={styles.bold}>
|
||||
({warningCounts.get(this.props.warning)}){" "}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<View
|
||||
style={styles.warningBox}
|
||||
ref={container => { this.container = container; }}
|
||||
{...this.panGesture.panHandlers}>
|
||||
<TouchableOpacity
|
||||
onPress={this.props.onOpened}>
|
||||
<View>
|
||||
<Text
|
||||
style={styles.warningText}
|
||||
numberOfLines={2}
|
||||
ref={text => { this.text = text; }}>
|
||||
{countText}
|
||||
{this.props.warning}
|
||||
</Text>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
<View
|
||||
ref={closeButton => { this.closeButton = closeButton; }}
|
||||
style={styles.closeButton}>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
this.dismissalSpring.setEndValue(1);
|
||||
}}>
|
||||
<Text style={styles.closeButtonText}>✕</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
var WarningBoxOpened = React.createClass({
|
||||
render: function() {
|
||||
var countText;
|
||||
if (warningCounts.get(this.props.warning) > 1) {
|
||||
countText = (
|
||||
<Text style={styles.bold}>
|
||||
({warningCounts.get(this.props.warning)}){" "}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.9}
|
||||
onPress={this.props.onClose}>
|
||||
<View style={styles.yellowBox}>
|
||||
<Text style={styles.yellowBoxText}>
|
||||
{countText}
|
||||
{this.props.warning}
|
||||
</Text>
|
||||
<View style={styles.yellowBoxButtons}>
|
||||
<View style={styles.yellowBoxButton}>
|
||||
<TouchableOpacity
|
||||
onPress={this.props.onDismissed}>
|
||||
<Text style={styles.yellowBoxButtonText}>
|
||||
Dismiss
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.yellowBoxButton}>
|
||||
<TouchableOpacity
|
||||
onPress={this.props.onIgnored}>
|
||||
<Text style={styles.yellowBoxButtonText}>
|
||||
Ignore
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var canMountWarningBox = true;
|
||||
|
||||
var WarningBox = React.createClass({
|
||||
getInitialState: function() {
|
||||
return {
|
||||
totalWarningCount,
|
||||
openWarning: null,
|
||||
};
|
||||
},
|
||||
componentWillMount: function() {
|
||||
if (console.yellowBoxResetIgnored) {
|
||||
AsyncStorage.setItem(IGNORED_WARNINGS_KEY, '[]', function(err) {
|
||||
if (err) {
|
||||
console.warn('Could not reset ignored warnings.', err);
|
||||
}
|
||||
});
|
||||
ignoredWarnings = [];
|
||||
}
|
||||
},
|
||||
componentDidMount: function() {
|
||||
invariant(
|
||||
canMountWarningBox,
|
||||
'There can only be one WarningBox'
|
||||
);
|
||||
canMountWarningBox = false;
|
||||
warningCountEvents.addListener(
|
||||
'count',
|
||||
this._onWarningCount
|
||||
);
|
||||
},
|
||||
componentWillUnmount: function() {
|
||||
warningCountEvents.removeAllListeners();
|
||||
canMountWarningBox = true;
|
||||
},
|
||||
_onWarningCount: function(totalWarningCount) {
|
||||
// Must use setImmediate because warnings often happen during render and
|
||||
// state cannot be set while rendering
|
||||
setImmediate(() => {
|
||||
this.setState({ totalWarningCount, });
|
||||
});
|
||||
},
|
||||
_onDismiss: function(warning) {
|
||||
warningCounts.delete(warning);
|
||||
this.setState({
|
||||
openWarning: null,
|
||||
});
|
||||
},
|
||||
render: function() {
|
||||
if (warningCounts.size === 0) {
|
||||
return <View />;
|
||||
}
|
||||
if (this.state.openWarning) {
|
||||
return (
|
||||
<WarningBoxOpened
|
||||
warning={this.state.openWarning}
|
||||
onClose={() => {
|
||||
this.setState({ openWarning: null });
|
||||
}}
|
||||
onDismissed={this._onDismiss.bind(this, this.state.openWarning)}
|
||||
onIgnored={() => {
|
||||
ignoredWarnings.push(this.state.openWarning);
|
||||
saveIgnoredWarnings();
|
||||
this._onDismiss(this.state.openWarning);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
var warningRows = [];
|
||||
warningCounts.forEach((count, warning) => {
|
||||
warningRows.push(
|
||||
<WarningRow
|
||||
key={warning}
|
||||
onOpened={() => {
|
||||
this.setState({ openWarning: warning });
|
||||
}}
|
||||
onDismissed={this._onDismiss.bind(this, warning)}
|
||||
warning={warning}
|
||||
/>
|
||||
);
|
||||
});
|
||||
return (
|
||||
<View style={styles.warningContainer}>
|
||||
{warningRows}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
bold: {
|
||||
fontWeight: 'bold',
|
||||
},
|
||||
closeButton: {
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
height: 46,
|
||||
width: 46,
|
||||
},
|
||||
closeButtonText: {
|
||||
color: 'white',
|
||||
fontSize: 32,
|
||||
position: 'relative',
|
||||
left: 8,
|
||||
},
|
||||
warningContainer: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0
|
||||
},
|
||||
warningBox: {
|
||||
position: 'relative',
|
||||
backgroundColor: 'rgba(171, 124, 36, 0.9)',
|
||||
flex: 1,
|
||||
height: 46,
|
||||
},
|
||||
warningText: {
|
||||
color: 'white',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
marginLeft: 15,
|
||||
marginRight: 46,
|
||||
top: 7,
|
||||
},
|
||||
yellowBox: {
|
||||
backgroundColor: 'rgba(171, 124, 36, 0.9)',
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
padding: 15,
|
||||
paddingTop: 35,
|
||||
},
|
||||
yellowBoxText: {
|
||||
color: 'white',
|
||||
fontSize: 20,
|
||||
},
|
||||
yellowBoxButtons: {
|
||||
flexDirection: 'row',
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
},
|
||||
yellowBoxButton: {
|
||||
flex: 1,
|
||||
padding: 25,
|
||||
},
|
||||
yellowBoxButtonText: {
|
||||
color: 'white',
|
||||
fontSize: 16,
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = WarningBox;
|
|
@ -11,7 +11,12 @@
|
|||
*/
|
||||
'use strict';
|
||||
|
||||
require('ExceptionsManager').installConsoleErrorReporter();
|
||||
|
||||
var React = require('React');
|
||||
var StyleSheet = require('StyleSheet');
|
||||
var View = require('View');
|
||||
var WarningBox = require('WarningBox');
|
||||
|
||||
var invariant = require('invariant');
|
||||
|
||||
|
@ -24,12 +29,27 @@ function renderApplication<D, P, S>(
|
|||
rootTag,
|
||||
'Expect to have a valid rootTag, instead got ', rootTag
|
||||
);
|
||||
var shouldRenderWarningBox = __DEV__ && console.yellowBoxEnabled;
|
||||
var warningBox = shouldRenderWarningBox ? <WarningBox /> : null;
|
||||
React.render(
|
||||
<RootComponent
|
||||
{...initialProps}
|
||||
/>,
|
||||
<View style={styles.appContainer}>
|
||||
<RootComponent
|
||||
{...initialProps}
|
||||
/>
|
||||
{warningBox}
|
||||
</View>,
|
||||
rootTag
|
||||
);
|
||||
}
|
||||
|
||||
var styles = StyleSheet.create({
|
||||
appContainer: {
|
||||
position: 'absolute',
|
||||
left: 0,
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = renderApplication;
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
131B6AC01AF0CD0600FFC3E0 /* RCTTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6ABD1AF0CD0600FFC3E0 /* RCTTextView.m */; };
|
||||
131B6AC11AF0CD0600FFC3E0 /* RCTTextViewManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 131B6ABF1AF0CD0600FFC3E0 /* RCTTextViewManager.m */; };
|
||||
58B511CE1A9E6C5C00147676 /* RCTRawTextManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511C71A9E6C5C00147676 /* RCTRawTextManager.m */; };
|
||||
58B511CF1A9E6C5C00147676 /* RCTShadowRawText.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511C91A9E6C5C00147676 /* RCTShadowRawText.m */; };
|
||||
58B511D01A9E6C5C00147676 /* RCTShadowText.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B511CB1A9E6C5C00147676 /* RCTShadowText.m */; };
|
||||
|
@ -27,6 +29,10 @@
|
|||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
131B6ABC1AF0CD0600FFC3E0 /* RCTTextView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextView.h; sourceTree = "<group>"; };
|
||||
131B6ABD1AF0CD0600FFC3E0 /* RCTTextView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextView.m; sourceTree = "<group>"; };
|
||||
131B6ABE1AF0CD0600FFC3E0 /* RCTTextViewManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTTextViewManager.h; sourceTree = "<group>"; };
|
||||
131B6ABF1AF0CD0600FFC3E0 /* RCTTextViewManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTTextViewManager.m; sourceTree = "<group>"; };
|
||||
58B5119B1A9E6C1200147676 /* libRCTText.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTText.a; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
58B511C61A9E6C5C00147676 /* RCTRawTextManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTRawTextManager.h; sourceTree = "<group>"; };
|
||||
58B511C71A9E6C5C00147676 /* RCTRawTextManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTRawTextManager.m; sourceTree = "<group>"; };
|
||||
|
@ -64,6 +70,10 @@
|
|||
58B512141A9E6EFF00147676 /* RCTText.m */,
|
||||
58B511CC1A9E6C5C00147676 /* RCTTextManager.h */,
|
||||
58B511CD1A9E6C5C00147676 /* RCTTextManager.m */,
|
||||
131B6ABC1AF0CD0600FFC3E0 /* RCTTextView.h */,
|
||||
131B6ABD1AF0CD0600FFC3E0 /* RCTTextView.m */,
|
||||
131B6ABE1AF0CD0600FFC3E0 /* RCTTextViewManager.h */,
|
||||
131B6ABF1AF0CD0600FFC3E0 /* RCTTextViewManager.m */,
|
||||
58B5119C1A9E6C1200147676 /* Products */,
|
||||
);
|
||||
indentWidth = 2;
|
||||
|
@ -135,8 +145,10 @@
|
|||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
58B511D11A9E6C5C00147676 /* RCTTextManager.m in Sources */,
|
||||
131B6AC01AF0CD0600FFC3E0 /* RCTTextView.m in Sources */,
|
||||
58B511CE1A9E6C5C00147676 /* RCTRawTextManager.m in Sources */,
|
||||
58B512161A9E6EFF00147676 /* RCTText.m in Sources */,
|
||||
131B6AC11AF0CD0600FFC3E0 /* RCTTextViewManager.m in Sources */,
|
||||
58B511CF1A9E6C5C00147676 /* RCTShadowRawText.m in Sources */,
|
||||
58B511D01A9E6C5C00147676 /* RCTShadowText.m in Sources */,
|
||||
);
|
||||
|
|
|
@ -12,4 +12,3 @@
|
|||
@interface RCTTextManager : RCTViewManager
|
||||
|
||||
@end
|
||||
|
||||
|
|
|
@ -123,7 +123,7 @@ RCT_CUSTOM_SHADOW_PROPERTY(numberOfLines, NSInteger, RCTShadowText)
|
|||
UIEdgeInsets padding = shadowView.paddingAsInsets;
|
||||
|
||||
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
RCTText *text = (RCTText *)viewRegistry[reactTag];
|
||||
RCTText *text = viewRegistry[reactTag];
|
||||
text.contentInset = padding;
|
||||
text.layoutManager = shadowView.layoutManager;
|
||||
text.textContainer = shadowView.textContainer;
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "RCTView.h"
|
||||
#import "UIView+React.h"
|
||||
|
||||
@class RCTEventDispatcher;
|
||||
|
||||
@interface RCTTextView : RCTView <UITextViewDelegate>
|
||||
|
||||
@property (nonatomic, assign) BOOL autoCorrect;
|
||||
@property (nonatomic, assign) BOOL clearTextOnFocus;
|
||||
@property (nonatomic, assign) BOOL selectTextOnFocus;
|
||||
@property (nonatomic, assign) UIEdgeInsets contentInset;
|
||||
@property (nonatomic, assign) BOOL automaticallyAdjustContentInsets;
|
||||
@property (nonatomic, strong) UIColor *placeholderTextColor;
|
||||
@property (nonatomic, assign) UIFont *font;
|
||||
|
||||
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
@end
|
|
@ -0,0 +1,216 @@
|
|||
/**
|
||||
* 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 "RCTTextView.h"
|
||||
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTEventDispatcher.h"
|
||||
#import "RCTUtils.h"
|
||||
#import "UIView+React.h"
|
||||
|
||||
@implementation RCTTextView
|
||||
{
|
||||
RCTEventDispatcher *_eventDispatcher;
|
||||
BOOL _jsRequestingFirstResponder;
|
||||
NSString *_placeholder;
|
||||
UITextView *_placeholderView;
|
||||
UITextView *_textView;
|
||||
}
|
||||
|
||||
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
|
||||
{
|
||||
if ((self = [super initWithFrame:CGRectZero])) {
|
||||
_contentInset = UIEdgeInsetsZero;
|
||||
_eventDispatcher = eventDispatcher;
|
||||
_placeholderTextColor = [self defaultPlaceholderTextColor];
|
||||
|
||||
_textView = [[UITextView alloc] initWithFrame:self.bounds];
|
||||
_textView.backgroundColor = [UIColor clearColor];
|
||||
_textView.delegate = self;
|
||||
[self addSubview:_textView];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)updateFrames
|
||||
{
|
||||
// Adjust the insets so that they are as close as possible to single-line
|
||||
// RCTTextField defaults
|
||||
UIEdgeInsets adjustedInset = (UIEdgeInsets){
|
||||
_contentInset.top - 5, _contentInset.left - 4,
|
||||
_contentInset.bottom, _contentInset.right
|
||||
};
|
||||
|
||||
[_textView setFrame:UIEdgeInsetsInsetRect(self.bounds, adjustedInset)];
|
||||
[_placeholderView setFrame:UIEdgeInsetsInsetRect(self.bounds, adjustedInset)];
|
||||
}
|
||||
|
||||
- (void)updatePlaceholder
|
||||
{
|
||||
[_placeholderView removeFromSuperview];
|
||||
_placeholderView = nil;
|
||||
|
||||
if (_placeholder) {
|
||||
_placeholderView = [[UITextView alloc] initWithFrame:self.bounds];
|
||||
_placeholderView.backgroundColor = [UIColor clearColor];
|
||||
_placeholderView.scrollEnabled = false;
|
||||
_placeholderView.attributedText =
|
||||
[[NSAttributedString alloc] initWithString:_placeholder attributes:@{
|
||||
NSFontAttributeName : (_textView.font ? _textView.font : [self defaultPlaceholderFont]),
|
||||
NSForegroundColorAttributeName : _placeholderTextColor
|
||||
}];
|
||||
|
||||
[self insertSubview:_placeholderView belowSubview:_textView];
|
||||
[self _setPlaceholderVisibility];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setFont:(UIFont *)font
|
||||
{
|
||||
_font = font;
|
||||
_textView.font = _font;
|
||||
[self updatePlaceholder];
|
||||
}
|
||||
|
||||
- (void)setTextColor:(UIColor *)textColor
|
||||
{
|
||||
_textView.textColor = textColor;
|
||||
}
|
||||
|
||||
- (void)setPlaceholder:(NSString *)placeholder
|
||||
{
|
||||
_placeholder = placeholder;
|
||||
[self updatePlaceholder];
|
||||
}
|
||||
|
||||
- (void)setPlaceholderTextColor:(UIColor *)placeholderTextColor
|
||||
{
|
||||
if (placeholderTextColor) {
|
||||
_placeholderTextColor = placeholderTextColor;
|
||||
} else {
|
||||
_placeholderTextColor = [self defaultPlaceholderTextColor];
|
||||
}
|
||||
[self updatePlaceholder];
|
||||
}
|
||||
|
||||
- (void)setContentInset:(UIEdgeInsets)contentInset
|
||||
{
|
||||
_contentInset = contentInset;
|
||||
[self updateFrames];
|
||||
}
|
||||
|
||||
- (void)setText:(NSString *)text
|
||||
{
|
||||
if (![text isEqualToString:_textView.text]) {
|
||||
[_textView setText:text];
|
||||
[self _setPlaceholderVisibility];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)_setPlaceholderVisibility
|
||||
{
|
||||
if (_textView.text.length > 0) {
|
||||
[_placeholderView setHidden:YES];
|
||||
} else {
|
||||
[_placeholderView setHidden:NO];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setAutoCorrect:(BOOL)autoCorrect
|
||||
{
|
||||
_textView.autocorrectionType = (autoCorrect ? UITextAutocorrectionTypeYes : UITextAutocorrectionTypeNo);
|
||||
}
|
||||
|
||||
- (BOOL)autoCorrect
|
||||
{
|
||||
return _textView.autocorrectionType == UITextAutocorrectionTypeYes;
|
||||
}
|
||||
|
||||
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView
|
||||
{
|
||||
if (_selectTextOnFocus) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[textView selectAll:nil];
|
||||
});
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)textViewDidBeginEditing:(UITextView *)textView
|
||||
{
|
||||
if (_clearTextOnFocus) {
|
||||
[_textView setText:@""];
|
||||
_textView.text = @"";
|
||||
[self _setPlaceholderVisibility];
|
||||
}
|
||||
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeFocus
|
||||
reactTag:self.reactTag
|
||||
text:textView.text];
|
||||
}
|
||||
|
||||
- (void)textViewDidChange:(UITextView *)textView
|
||||
{
|
||||
[self _setPlaceholderVisibility];
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeChange
|
||||
reactTag:self.reactTag
|
||||
text:textView.text];
|
||||
|
||||
}
|
||||
|
||||
- (void)textViewDidEndEditing:(UITextView *)textView
|
||||
{
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeEnd
|
||||
reactTag:self.reactTag
|
||||
text:textView.text];
|
||||
}
|
||||
|
||||
- (BOOL)becomeFirstResponder
|
||||
{
|
||||
_jsRequestingFirstResponder = YES;
|
||||
BOOL result = [_textView becomeFirstResponder];
|
||||
_jsRequestingFirstResponder = NO;
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL)resignFirstResponder
|
||||
{
|
||||
[super resignFirstResponder];
|
||||
BOOL result = [_textView resignFirstResponder];
|
||||
if (result) {
|
||||
[_eventDispatcher sendTextEventWithType:RCTTextEventTypeBlur
|
||||
reactTag:self.reactTag
|
||||
text:_textView.text];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (void)layoutSubviews
|
||||
{
|
||||
[super layoutSubviews];
|
||||
[self updateFrames];
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeFirstResponder
|
||||
{
|
||||
return _jsRequestingFirstResponder;
|
||||
}
|
||||
|
||||
- (UIFont *)defaultPlaceholderFont
|
||||
{
|
||||
return [UIFont fontWithName:@"Helvetica" size:17];
|
||||
}
|
||||
|
||||
- (UIColor *)defaultPlaceholderTextColor
|
||||
{
|
||||
return [UIColor colorWithRed:0.0/255.0 green:0.0/255.0 blue:0.098/255.0 alpha:0.22];
|
||||
}
|
||||
|
||||
@end
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "RCTViewManager.h"
|
||||
|
||||
@interface RCTTextViewManager : RCTViewManager
|
||||
|
||||
@end
|
|
@ -0,0 +1,65 @@
|
|||
/**
|
||||
* 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 "RCTTextViewManager.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTShadowView.h"
|
||||
#import "RCTSparseArray.h"
|
||||
#import "RCTTextView.h"
|
||||
|
||||
@implementation RCTTextViewManager
|
||||
|
||||
RCT_EXPORT_MODULE()
|
||||
|
||||
- (UIView *)view
|
||||
{
|
||||
return [[RCTTextView alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
|
||||
}
|
||||
|
||||
RCT_EXPORT_VIEW_PROPERTY(autoCorrect, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(editable, textView.editable, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(placeholder, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(placeholderTextColor, UIColor)
|
||||
RCT_EXPORT_VIEW_PROPERTY(text, NSString)
|
||||
RCT_EXPORT_VIEW_PROPERTY(clearTextOnFocus, BOOL)
|
||||
RCT_EXPORT_VIEW_PROPERTY(selectTextOnFocus, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(keyboardType, textView.keyboardType, UIKeyboardType)
|
||||
RCT_REMAP_VIEW_PROPERTY(returnKeyType, textView.returnKeyType, UIReturnKeyType)
|
||||
RCT_REMAP_VIEW_PROPERTY(enablesReturnKeyAutomatically, textView.enablesReturnKeyAutomatically, BOOL)
|
||||
RCT_REMAP_VIEW_PROPERTY(color, textColor, UIColor)
|
||||
RCT_REMAP_VIEW_PROPERTY(autoCapitalize, textView.autocapitalizationType, UITextAutocapitalizationType)
|
||||
RCT_CUSTOM_VIEW_PROPERTY(fontSize, CGFloat, RCTTextView)
|
||||
{
|
||||
view.font = [RCTConvert UIFont:view.font withSize:json ?: @(defaultView.font.pointSize)];
|
||||
}
|
||||
RCT_CUSTOM_VIEW_PROPERTY(fontWeight, NSString, RCTTextView)
|
||||
{
|
||||
view.font = [RCTConvert UIFont:view.font withWeight:json]; // defaults to normal
|
||||
}
|
||||
RCT_CUSTOM_VIEW_PROPERTY(fontStyle, NSString, RCTTextView)
|
||||
{
|
||||
view.font = [RCTConvert UIFont:view.font withStyle:json]; // defaults to normal
|
||||
}
|
||||
RCT_CUSTOM_VIEW_PROPERTY(fontFamily, NSString, RCTTextView)
|
||||
{
|
||||
view.font = [RCTConvert UIFont:view.font withFamily:json ?: defaultView.font.familyName];
|
||||
}
|
||||
|
||||
- (RCTViewManagerUIBlock)uiBlockToAmendWithShadowView:(RCTShadowView *)shadowView
|
||||
{
|
||||
NSNumber *reactTag = shadowView.reactTag;
|
||||
UIEdgeInsets padding = shadowView.paddingAsInsets;
|
||||
return ^(RCTUIManager *uiManager, RCTSparseArray *viewRegistry) {
|
||||
((RCTTextView *)viewRegistry[reactTag]).contentInset = padding;
|
||||
};
|
||||
}
|
||||
|
||||
@end
|
|
@ -33,7 +33,7 @@ var viewConfig = {
|
|||
};
|
||||
|
||||
/**
|
||||
* A react component for displaying text which supports nesting,
|
||||
* A React component for displaying text which supports nesting,
|
||||
* styling, and touch handling. In the following example, the nested title and
|
||||
* body text will inherit the `fontFamily` from `styles.baseText`, but the title
|
||||
* provides its own additional styles. The title and body will stack on top of
|
||||
|
|
|
@ -20,7 +20,7 @@ var dimensions = NativeModules.UIManager.Dimensions;
|
|||
// We calculate the window dimensions in JS so that we don't encounter loss of
|
||||
// precision in transferring the dimensions (which could be non-integers) over
|
||||
// the bridge.
|
||||
if (dimensions.windowPhysicalPixels) {
|
||||
if (dimensions && dimensions.windowPhysicalPixels) {
|
||||
// parse/stringify => Clone hack
|
||||
dimensions = JSON.parse(JSON.stringify(dimensions));
|
||||
|
||||
|
|
|
@ -21,6 +21,9 @@ var JSTimersExecution = require('JSTimersExecution');
|
|||
|
||||
var INTERNAL_ERROR = 'Error in MessageQueue implementation';
|
||||
|
||||
// Prints all bridge traffic to console.log
|
||||
var DEBUG_SPY_MODE = false;
|
||||
|
||||
type ModulesConfig = {
|
||||
[key:string]: {
|
||||
moduleID: number;
|
||||
|
@ -263,6 +266,9 @@ var MessageQueueMixin = {
|
|||
'both the success callback and the error callback.',
|
||||
cbID
|
||||
);
|
||||
if (DEBUG_SPY_MODE) {
|
||||
console.log('N->JS: Callback#' + cbID + '(' + JSON.stringify(args) + ')');
|
||||
}
|
||||
cb.apply(scope, args);
|
||||
} catch(ie_requires_catch) {
|
||||
throw ie_requires_catch;
|
||||
|
@ -292,6 +298,11 @@ var MessageQueueMixin = {
|
|||
var moduleName = this._localModuleIDToModuleName[moduleID];
|
||||
|
||||
var methodName = this._localModuleNameToMethodIDToName[moduleName][methodID];
|
||||
if (DEBUG_SPY_MODE) {
|
||||
console.log(
|
||||
'N->JS: ' + moduleName + '.' + methodName +
|
||||
'(' + JSON.stringify(params) + ')');
|
||||
}
|
||||
var ret = jsCall(this._requireFunc(moduleName), methodName, params);
|
||||
|
||||
return ret;
|
||||
|
@ -460,6 +471,17 @@ var MessageQueueMixin = {
|
|||
var ret = currentOutgoingItems[REQUEST_MODULE_IDS].length ||
|
||||
currentOutgoingItems[RESPONSE_RETURN_VALUES].length ? currentOutgoingItems : null;
|
||||
|
||||
if (DEBUG_SPY_MODE && ret) {
|
||||
for (var i = 0; i < currentOutgoingItems[0].length; i++) {
|
||||
var moduleName = this._remoteModuleIDToModuleName[currentOutgoingItems[0][i]];
|
||||
var methodName =
|
||||
this._remoteModuleNameToMethodIDToName[moduleName][currentOutgoingItems[1][i]];
|
||||
console.log(
|
||||
'JS->N: ' + moduleName + '.' + methodName +
|
||||
'(' + JSON.stringify(currentOutgoingItems[2][i]) + ')');
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
},
|
||||
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* 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 stringifySafe
|
||||
* @flow
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Tries to stringify with JSON.stringify and toString, but catches exceptions
|
||||
* (e.g. from circular objects) and always returns a string and never throws.
|
||||
*/
|
||||
function stringifySafe(arg: any): string {
|
||||
var ret;
|
||||
if (arg === undefined) {
|
||||
ret = 'undefined';
|
||||
} else if (arg === null) {
|
||||
ret = 'null';
|
||||
} else if (typeof arg === 'string') {
|
||||
ret = '"' + arg + '"';
|
||||
} else {
|
||||
// Perform a try catch, just in case the object has a circular
|
||||
// reference or stringify throws for some other reason.
|
||||
try {
|
||||
ret = JSON.stringify(arg);
|
||||
} catch (e) {
|
||||
if (typeof arg.toString === 'function') {
|
||||
try {
|
||||
ret = arg.toString();
|
||||
} catch (E) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret || '["' + typeof arg + '" failed to stringify]';
|
||||
}
|
||||
|
||||
module.exports = stringifySafe;
|
|
@ -27,6 +27,7 @@ var ReactNative = Object.assign(Object.create(require('React')), {
|
|||
NavigatorIOS: require('NavigatorIOS'),
|
||||
PickerIOS: require('PickerIOS'),
|
||||
Navigator: require('Navigator'),
|
||||
SegmentedControlIOS: require('SegmentedControlIOS'),
|
||||
ScrollView: require('ScrollView'),
|
||||
SliderIOS: require('SliderIOS'),
|
||||
SwitchIOS: require('SwitchIOS'),
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
* worker thread.
|
||||
*
|
||||
* The only other requirement of a platform/environment is that it always be
|
||||
* possible to extract the react rootNodeID in a blocking manner (see
|
||||
* possible to extract the React rootNodeID in a blocking manner (see
|
||||
* `getRootNodeID`).
|
||||
*
|
||||
* +------------------+ +------------------+ +------------------+
|
||||
|
|
|
@ -436,6 +436,7 @@ var TouchableMixin = {
|
|||
if (isTouchWithinActive) {
|
||||
this._receiveSignal(Signals.ENTER_PRESS_RECT, e);
|
||||
} else {
|
||||
this._cancelLongPressDelayTimeout();
|
||||
this._receiveSignal(Signals.LEAVE_PRESS_RECT, e);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -31,6 +31,8 @@
|
|||
NSString *const RCTReloadNotification = @"RCTReloadNotification";
|
||||
NSString *const RCTJavaScriptDidLoadNotification = @"RCTJavaScriptDidLoadNotification";
|
||||
|
||||
dispatch_queue_t const RCTJSThread = nil;
|
||||
|
||||
/**
|
||||
* Must be kept in sync with `MessageQueue.js`.
|
||||
*/
|
||||
|
@ -189,6 +191,7 @@ static NSArray *RCTBridgeModuleClassesByModuleID(void)
|
|||
superclass = class_getSuperclass(superclass);
|
||||
}
|
||||
}
|
||||
free(classes);
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -353,7 +356,7 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
|||
|
||||
#define RCT_CONVERT_CASE(_value, _type) \
|
||||
case _value: { \
|
||||
_type (*convert)(id, SEL, id) = (typeof(convert))[RCTConvert methodForSelector:selector]; \
|
||||
_type (*convert)(id, SEL, id) = (typeof(convert))objc_msgSend; \
|
||||
RCT_ARG_BLOCK( _type value = convert([RCTConvert class], selector, json); ) \
|
||||
break; \
|
||||
}
|
||||
|
@ -375,12 +378,27 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
|||
RCT_CONVERT_CASE('B', BOOL)
|
||||
RCT_CONVERT_CASE('@', id)
|
||||
RCT_CONVERT_CASE('^', void *)
|
||||
case '{':
|
||||
RCTAssert(NO, @"Argument %zd of %C[%@ %@] is defined as %@, however RCT_EXPORT_METHOD() "
|
||||
"does not currently support struct-type arguments.", i - 2,
|
||||
[reactMethodName characterAtIndex:0], _moduleClassName,
|
||||
objCMethodName, argumentName);
|
||||
break;
|
||||
|
||||
case '{': {
|
||||
[argumentBlocks addObject:^(RCTBridge *bridge, NSNumber *context, NSInvocation *invocation, NSUInteger index, id json) {
|
||||
NSUInteger size;
|
||||
NSGetSizeAndAlignment(argumentType, &size, NULL);
|
||||
void *returnValue = malloc(size);
|
||||
NSMethodSignature *methodSignature = [RCTConvert methodSignatureForSelector:selector];
|
||||
NSInvocation *_invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
|
||||
[_invocation setTarget:[RCTConvert class]];
|
||||
[_invocation setSelector:selector];
|
||||
[_invocation setArgument:&json atIndex:2];
|
||||
[_invocation invoke];
|
||||
[_invocation getReturnValue:returnValue];
|
||||
|
||||
[invocation setArgument:returnValue atIndex:index];
|
||||
|
||||
free(returnValue);
|
||||
}];
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
defaultCase(argumentType);
|
||||
}
|
||||
|
@ -436,6 +454,10 @@ static NSString *RCTStringUpToFirstArgument(NSString *methodName)
|
|||
RCT_SIMPLE_CASE('d', double, doubleValue)
|
||||
RCT_SIMPLE_CASE('B', BOOL, boolValue)
|
||||
|
||||
case '{':
|
||||
RCTLogMustFix(@"Cannot convert JSON to struct %s", argumentType);
|
||||
break;
|
||||
|
||||
default:
|
||||
defaultCase(argumentType);
|
||||
}
|
||||
|
@ -650,6 +672,8 @@ static NSDictionary *RCTRemoteModulesConfig(NSDictionary *modulesByName)
|
|||
*/
|
||||
static NSMutableDictionary *RCTLocalModuleIDs;
|
||||
static NSMutableDictionary *RCTLocalMethodIDs;
|
||||
static NSMutableArray *RCTLocalModuleNames;
|
||||
static NSMutableArray *RCTLocalMethodNames;
|
||||
static NSDictionary *RCTLocalModulesConfig()
|
||||
{
|
||||
static NSMutableDictionary *localModules;
|
||||
|
@ -658,6 +682,8 @@ static NSDictionary *RCTLocalModulesConfig()
|
|||
|
||||
RCTLocalModuleIDs = [[NSMutableDictionary alloc] init];
|
||||
RCTLocalMethodIDs = [[NSMutableDictionary alloc] init];
|
||||
RCTLocalModuleNames = [[NSMutableArray alloc] init];
|
||||
RCTLocalMethodNames = [[NSMutableArray alloc] init];
|
||||
|
||||
localModules = [[NSMutableDictionary alloc] init];
|
||||
for (NSString *moduleDotMethod in RCTJSMethods()) {
|
||||
|
@ -689,6 +715,8 @@ static NSDictionary *RCTLocalModulesConfig()
|
|||
// Add module and method lookup
|
||||
RCTLocalModuleIDs[moduleDotMethod] = module[@"moduleID"];
|
||||
RCTLocalMethodIDs[moduleDotMethod] = methods[methodName][@"methodID"];
|
||||
[RCTLocalModuleNames addObject:moduleName];
|
||||
[RCTLocalMethodNames addObject:methodName];
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -795,6 +823,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
_bundleURL = bundleURL;
|
||||
_moduleProvider = block;
|
||||
_launchOptions = [launchOptions copy];
|
||||
|
||||
[self setUp];
|
||||
[self bindKeys];
|
||||
}
|
||||
|
@ -872,6 +901,8 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
dispatch_queue_t queue = [module methodQueue];
|
||||
if (queue) {
|
||||
_queuesByID[moduleID] = queue;
|
||||
} else {
|
||||
_queuesByID[moduleID] = [NSNull null];
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
@ -1047,7 +1078,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
#pragma mark - RCTBridge methods
|
||||
|
||||
/**
|
||||
* Like JS::call, for objective-c.
|
||||
* Public. Can be invoked from any thread.
|
||||
*/
|
||||
- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args
|
||||
{
|
||||
|
@ -1058,12 +1089,10 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
NSNumber *methodID = RCTLocalMethodIDs[moduleDotMethod];
|
||||
RCTAssert(methodID != nil, @"Method '%@' not registered.", moduleDotMethod);
|
||||
|
||||
if (!_loading) {
|
||||
[self _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, args ?: @[]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
}
|
||||
[self _invokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, args ?: @[]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1081,10 +1110,17 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
|
||||
if (!_loading) {
|
||||
#if BATCHED_BRIDGE
|
||||
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, @[@[timer]]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
dispatch_block_t block = ^{
|
||||
[self _actuallyInvokeAndProcessModule:@"BatchedBridge"
|
||||
method:@"callFunctionReturnFlushedQueue"
|
||||
arguments:@[moduleID, methodID, @[@[timer]]]
|
||||
context:RCTGetExecutorID(_javaScriptExecutor)];
|
||||
};
|
||||
if ([_javaScriptExecutor respondsToSelector:@selector(executeAsyncBlockOnJavaScriptQueue:)]) {
|
||||
[_javaScriptExecutor executeAsyncBlockOnJavaScriptQueue:block];
|
||||
} else {
|
||||
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
|
@ -1128,33 +1164,93 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
|
||||
#pragma mark - Payload Generation
|
||||
|
||||
- (void)dispatchBlock:(dispatch_block_t)block forModule:(NSNumber *)moduleID
|
||||
{
|
||||
id queue = _queuesByID[moduleID];
|
||||
if (queue == [NSNull null]) {
|
||||
[_javaScriptExecutor executeBlockOnJavaScriptQueue:block];
|
||||
} else {
|
||||
dispatch_async(queue ?: _methodQueue, block);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by enqueueJSCall from any thread, or from _immediatelyCallTimer,
|
||||
* on the JS thread, but only in non-batched mode.
|
||||
*/
|
||||
- (void)_invokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
|
||||
{
|
||||
#if BATCHED_BRIDGE
|
||||
RCTProfileBeginEvent();
|
||||
|
||||
if ([module isEqualToString:@"RCTEventEmitter"]) {
|
||||
for (NSDictionary *call in _scheduledCalls) {
|
||||
if ([call[@"module"] isEqualToString:module] && [call[@"method"] isEqualToString:method] && [call[@"args"][0] isEqualToString:args[0]]) {
|
||||
[_scheduledCalls removeObject:call];
|
||||
__weak NSMutableArray *weakScheduledCalls = _scheduledCalls;
|
||||
__weak RCTSparseArray *weakScheduledCallbacks = _scheduledCallbacks;
|
||||
|
||||
[_javaScriptExecutor executeBlockOnJavaScriptQueue:^{
|
||||
RCTProfileBeginEvent();
|
||||
|
||||
NSMutableArray *scheduledCalls = weakScheduledCalls;
|
||||
RCTSparseArray *scheduledCallbacks = weakScheduledCallbacks;
|
||||
if (!scheduledCalls || !scheduledCallbacks) {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event deduping
|
||||
*
|
||||
* Right now we make a lot of assumptions about the arguments structure
|
||||
* so just iterate if it's a `callFunctionReturnFlushedQueue()`
|
||||
*/
|
||||
if ([method isEqualToString:@"callFunctionReturnFlushedQueue"]) {
|
||||
NSString *moduleName = RCTLocalModuleNames[[args[0] integerValue]];
|
||||
/**
|
||||
* Keep going if it any event emmiter, e.g. RCT(Device|NativeApp)?EventEmitter
|
||||
*/
|
||||
if ([moduleName hasSuffix:@"EventEmitter"]) {
|
||||
for (NSDictionary *call in [scheduledCalls copy]) {
|
||||
NSArray *callArgs = call[@"args"];
|
||||
/**
|
||||
* If it's the same module && method call on the bridge &&
|
||||
* the same EventEmitter module && method
|
||||
*/
|
||||
if (
|
||||
[call[@"module"] isEqualToString:module] &&
|
||||
[call[@"method"] isEqualToString:method] &&
|
||||
[callArgs[0] isEqual:args[0]] &&
|
||||
[callArgs[1] isEqual:args[1]]
|
||||
) {
|
||||
/**
|
||||
* args[2] contains the actual arguments for the event call, where
|
||||
* args[2][0] is the target for RCTEventEmitter or the eventName
|
||||
* for the other EventEmitters
|
||||
* if RCTEventEmitter we need to compare args[2][1] that will be
|
||||
* the eventName
|
||||
*/
|
||||
if (
|
||||
[args[2][0] isEqual:callArgs[2][0]] &&
|
||||
([moduleName isEqualToString:@"RCTEventEmitter"] ? [args[2][1] isEqual:callArgs[2][1]] : YES)
|
||||
) {
|
||||
[scheduledCalls removeObject:call];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
id call = @{
|
||||
@"module": module,
|
||||
@"method": method,
|
||||
@"args": args,
|
||||
@"context": context ?: @0,
|
||||
};
|
||||
id call = @{
|
||||
@"module": module,
|
||||
@"method": method,
|
||||
@"args": args,
|
||||
@"context": context ?: @0,
|
||||
};
|
||||
|
||||
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
|
||||
_scheduledCallbacks[args[0]] = call;
|
||||
} else {
|
||||
[_scheduledCalls addObject:call];
|
||||
}
|
||||
if ([method isEqualToString:@"invokeCallbackAndReturnFlushedQueue"]) {
|
||||
scheduledCallbacks[args[0]] = call;
|
||||
} else {
|
||||
[scheduledCalls addObject:call];
|
||||
}
|
||||
|
||||
RCTProfileEndEvent(@"enqueue_call", @"objc_call", call);
|
||||
RCTProfileEndEvent(@"enqueue_call", @"objc_call", call);
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)_actuallyInvokeAndProcessModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args context:(NSNumber *)context
|
||||
|
@ -1235,10 +1331,9 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
// TODO: batchDidComplete is only used by RCTUIManager - can we eliminate this special case?
|
||||
[_modulesByID enumerateObjectsUsingBlock:^(id<RCTBridgeModule> module, NSNumber *moduleID, BOOL *stop) {
|
||||
if ([module respondsToSelector:@selector(batchDidComplete)]) {
|
||||
dispatch_queue_t queue = _queuesByID[moduleID];
|
||||
dispatch_async(queue ?: _methodQueue, ^{
|
||||
[self dispatchBlock:^{
|
||||
[module batchDidComplete];
|
||||
});
|
||||
} forModule:moduleID];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
@ -1273,8 +1368,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
}
|
||||
|
||||
__weak RCTBridge *weakSelf = self;
|
||||
dispatch_queue_t queue = _queuesByID[moduleID];
|
||||
dispatch_async(queue ?: _methodQueue, ^{
|
||||
[self dispatchBlock:^{
|
||||
RCTProfileBeginEvent();
|
||||
__strong RCTBridge *strongSelf = weakSelf;
|
||||
|
||||
|
@ -1303,7 +1397,7 @@ static id<RCTJavaScriptExecutor> _latestJSExecutor;
|
|||
@"method": method.JSMethodName,
|
||||
@"selector": NSStringFromSelector(method.selector),
|
||||
});
|
||||
});
|
||||
} forModule:@(moduleID)];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
|
|
@ -17,6 +17,16 @@
|
|||
*/
|
||||
typedef void (^RCTResponseSenderBlock)(NSArray *response);
|
||||
|
||||
/**
|
||||
* This constant can be returned from +methodQueue to force module
|
||||
* methods to be called on the JavaScript thread. This can have serious
|
||||
* implications for performance, so only use this if you're sure it's what
|
||||
* you need.
|
||||
*
|
||||
* NOTE: RCTJSThread is not a real libdispatch queue
|
||||
*/
|
||||
extern const dispatch_queue_t RCTJSThread;
|
||||
|
||||
/**
|
||||
* Provides the interface needed to register a bridge module.
|
||||
*/
|
||||
|
|
|
@ -49,11 +49,11 @@ RCT_CONVERTER(NSString *, NSString, description)
|
|||
});
|
||||
NSNumber *number = [formatter numberFromString:json];
|
||||
if (!number) {
|
||||
RCTLogError(@"JSON String '%@' could not be interpreted as a number", json);
|
||||
RCTLogConvertError(json, "a number");
|
||||
}
|
||||
return number;
|
||||
} else if (json && json != [NSNull null]) {
|
||||
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a number", json, [json classForCoder]);
|
||||
RCTLogConvertError(json, "a number");
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
@ -66,30 +66,38 @@ RCT_CONVERTER(NSString *, NSString, description)
|
|||
|
||||
+ (NSURL *)NSURL:(id)json
|
||||
{
|
||||
if (!json || json == (id)kCFNull) {
|
||||
NSString *path = [self NSString:json];
|
||||
if (!path.length) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (![json isKindOfClass:[NSString class]]) {
|
||||
RCTLogError(@"Expected NSString for NSURL, received %@: %@", [json classForCoder], json);
|
||||
return nil;
|
||||
}
|
||||
@try { // NSURL has a history of crashing with bad input, so let's be safe
|
||||
|
||||
NSString *path = json;
|
||||
if ([path isAbsolutePath])
|
||||
{
|
||||
NSURL *URL = [NSURL URLWithString:path];
|
||||
if (URL.scheme) { // Was a well-formed absolute URL
|
||||
return URL;
|
||||
}
|
||||
|
||||
// Check if it has a scheme
|
||||
if ([path rangeOfString:@"[a-zA-Z][a-zA-Z._-]+:" options:NSRegularExpressionSearch].location == 0) {
|
||||
path = [path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
|
||||
URL = [NSURL URLWithString:path];
|
||||
if (URL) {
|
||||
return URL;
|
||||
}
|
||||
}
|
||||
|
||||
// Assume that it's a local path
|
||||
path = [path stringByRemovingPercentEncoding];
|
||||
if (![path isAbsolutePath]) {
|
||||
path = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:path];
|
||||
}
|
||||
return [NSURL fileURLWithPath:path];
|
||||
}
|
||||
else if ([path length])
|
||||
{
|
||||
NSURL *URL = [NSURL URLWithString:path relativeToURL:[[NSBundle mainBundle] resourceURL]];
|
||||
if ([URL isFileURL] && ![[NSFileManager defaultManager] fileExistsAtPath:[URL path]]) {
|
||||
RCTLogWarn(@"The file '%@' does not exist", URL);
|
||||
return nil;
|
||||
}
|
||||
return URL;
|
||||
@catch (__unused NSException *e) {
|
||||
RCTLogConvertError(json, "a valid URL");
|
||||
return nil;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (NSURLRequest *)NSURLRequest:(id)json
|
||||
|
@ -112,11 +120,12 @@ RCT_CONVERTER(NSString *, NSString, description)
|
|||
});
|
||||
NSDate *date = [formatter dateFromString:json];
|
||||
if (!date) {
|
||||
RCTLogError(@"JSON String '%@' could not be interpreted as a date. Expected format: YYYY-MM-DD'T'HH:mm:ss.sssZ", json);
|
||||
RCTLogError(@"JSON String '%@' could not be interpreted as a date. "
|
||||
"Expected format: YYYY-MM-DD'T'HH:mm:ss.sssZ", json);
|
||||
}
|
||||
return date;
|
||||
} else if (json && json != [NSNull null]) {
|
||||
RCTLogError(@"JSON value '%@' of class %@ could not be interpreted as a date", json, [json classForCoder]);
|
||||
RCTLogConvertError(json, "a date");
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
@ -698,50 +707,65 @@ static BOOL RCTFontIsCondensed(UIFont *font)
|
|||
const RCTFontWeight RCTDefaultFontWeight = UIFontWeightRegular;
|
||||
const CGFloat RCTDefaultFontSize = 14;
|
||||
|
||||
// Get existing properties
|
||||
// Initialize properties to defaults
|
||||
CGFloat fontSize = RCTDefaultFontSize;
|
||||
RCTFontWeight fontWeight = RCTDefaultFontWeight;
|
||||
NSString *familyName = RCTDefaultFontFamily;
|
||||
BOOL isItalic = NO;
|
||||
BOOL isCondensed = NO;
|
||||
RCTFontWeight fontWeight = RCTDefaultFontWeight;
|
||||
|
||||
if (font) {
|
||||
family = font.familyName;
|
||||
familyName = font.familyName ?: RCTDefaultFontFamily;
|
||||
fontSize = font.pointSize ?: RCTDefaultFontSize;
|
||||
fontWeight = RCTWeightOfFont(font);
|
||||
isItalic = RCTFontIsItalic(font);
|
||||
isCondensed = RCTFontIsCondensed(font);
|
||||
}
|
||||
|
||||
// Get font size
|
||||
fontSize = [self CGFloat:size] ?: fontSize;
|
||||
|
||||
// Get font family
|
||||
familyName = [self NSString:family] ?: familyName;
|
||||
|
||||
// Get font style
|
||||
if (style) {
|
||||
isItalic = [self RCTFontStyle:style];
|
||||
}
|
||||
|
||||
// Get font size
|
||||
CGFloat fontSize = [self CGFloat:size] ?: RCTDefaultFontSize;
|
||||
|
||||
// Get font family
|
||||
NSString *familyName = [self NSString:family] ?: RCTDefaultFontFamily;
|
||||
if ([UIFont fontNamesForFamilyName:familyName].count == 0) {
|
||||
font = [UIFont fontWithName:familyName size:fontSize];
|
||||
if (font) {
|
||||
// It's actually a font name, not a font family name,
|
||||
// but we'll do what was meant, not what was said.
|
||||
familyName = font.familyName;
|
||||
NSDictionary *traits = [font.fontDescriptor objectForKey:UIFontDescriptorTraitsAttribute];
|
||||
fontWeight = [traits[UIFontWeightTrait] doubleValue];
|
||||
} else {
|
||||
// Not a valid font or family
|
||||
RCTLogError(@"Unrecognized font family '%@'", familyName);
|
||||
familyName = RCTDefaultFontFamily;
|
||||
}
|
||||
}
|
||||
|
||||
// Get font weight
|
||||
if (weight) {
|
||||
fontWeight = [self RCTFontWeight:weight];
|
||||
}
|
||||
|
||||
// Get closest match
|
||||
UIFont *bestMatch = font;
|
||||
CGFloat closestWeight = font ? RCTWeightOfFont(font) : INFINITY;
|
||||
// Gracefully handle being given a font name rather than font family, for
|
||||
// example: "Helvetica Light Oblique" rather than just "Helvetica".
|
||||
if ([UIFont fontNamesForFamilyName:familyName].count == 0) {
|
||||
font = [UIFont fontWithName:familyName size:fontSize];
|
||||
if (font) {
|
||||
// It's actually a font name, not a font family name,
|
||||
// but we'll do what was meant, not what was said.
|
||||
familyName = font.familyName;
|
||||
fontWeight = RCTWeightOfFont(font);
|
||||
isItalic = RCTFontIsItalic(font);
|
||||
isCondensed = RCTFontIsCondensed(font);
|
||||
} else {
|
||||
// Not a valid font or family
|
||||
RCTLogError(@"Unrecognized font family '%@'", familyName);
|
||||
familyName = RCTDefaultFontFamily;
|
||||
}
|
||||
}
|
||||
|
||||
// Get the closest font that matches the given weight for the fontFamily
|
||||
UIFont *bestMatch = [UIFont fontWithName:font.fontName size: fontSize];
|
||||
CGFloat closestWeight;
|
||||
|
||||
if (font && [font.familyName isEqualToString: familyName]) {
|
||||
closestWeight = RCTWeightOfFont(font);
|
||||
} else {
|
||||
closestWeight = INFINITY;
|
||||
}
|
||||
|
||||
for (NSString *name in [UIFont fontNamesForFamilyName:familyName]) {
|
||||
UIFont *match = [UIFont fontWithName:name size:fontSize];
|
||||
if (isItalic == RCTFontIsItalic(match) &&
|
||||
|
|
|
@ -50,7 +50,7 @@ typedef NS_ENUM(NSInteger, RCTScrollEventType) {
|
|||
|
||||
/**
|
||||
* Send a user input event. The body dictionary must contain a "target"
|
||||
* parameter, representing the react tag of the view sending the event
|
||||
* parameter, representing the React tag of the view sending the event
|
||||
*/
|
||||
- (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body;
|
||||
|
||||
|
|
|
@ -44,7 +44,7 @@ RCT_IMPORT_METHOD(RCTEventEmitter, receiveEvent);
|
|||
- (void)sendInputEventWithName:(NSString *)name body:(NSDictionary *)body
|
||||
{
|
||||
RCTAssert([body[@"target"] isKindOfClass:[NSNumber class]],
|
||||
@"Event body dictionary must include a 'target' property containing a react tag");
|
||||
@"Event body dictionary must include a 'target' property containing a React tag");
|
||||
|
||||
[_bridge enqueueJSCall:@"RCTEventEmitter.receiveEvent"
|
||||
args:body ? @[body[@"target"], name, body] : @[body[@"target"], name]];
|
||||
|
|
|
@ -49,6 +49,15 @@ typedef void (^RCTJavaScriptCallback)(id json, NSError *error);
|
|||
*/
|
||||
- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block;
|
||||
|
||||
@optional
|
||||
|
||||
/**
|
||||
* Special case for Timers + ContextExecutor - instead of the default
|
||||
* if jsthread then call else dispatch call on jsthread
|
||||
* ensure the call is made async on the jsthread
|
||||
*/
|
||||
- (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block;
|
||||
|
||||
@end
|
||||
|
||||
static const char *RCTJavaScriptExecutorID = "RCTJavaScriptExecutorID";
|
||||
|
|
|
@ -10,47 +10,15 @@
|
|||
#import "RCTJavaScriptLoader.h"
|
||||
|
||||
#import "RCTBridge.h"
|
||||
#import "RCTInvalidating.h"
|
||||
#import "RCTLog.h"
|
||||
#import "RCTRedBox.h"
|
||||
#import "RCTConvert.h"
|
||||
#import "RCTSourceCode.h"
|
||||
#import "RCTUtils.h"
|
||||
|
||||
#define NO_REMOTE_MODULE @"Could not fetch module bundle %@. Ensure node server is running.\n\nIf it timed out, try reloading."
|
||||
#define NO_LOCAL_BUNDLE @"Could not load local bundle %@. Ensure file exists."
|
||||
|
||||
#define CACHE_DIR @"RCTJSBundleCache"
|
||||
|
||||
#pragma mark - Application Engine
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* - Add window resize rotation events matching the DOM API.
|
||||
* - Device pixel ration hooks.
|
||||
* - Source maps.
|
||||
*/
|
||||
@implementation RCTJavaScriptLoader
|
||||
{
|
||||
__weak RCTBridge *_bridge;
|
||||
}
|
||||
|
||||
/**
|
||||
* `CADisplayLink` code copied from Ejecta but we've placed the JavaScriptCore
|
||||
* engine in its own dedicated thread.
|
||||
*
|
||||
* TODO: Try adding to the `RCTJavaScriptExecutor`'s thread runloop. Removes one
|
||||
* additional GCD dispatch per frame and likely makes it so that other UIThread
|
||||
* operations don't delay the dispatch (so we can begin working in JS much
|
||||
* faster.) Event handling must still be sent via a GCD dispatch, of course.
|
||||
*
|
||||
* We must add the display link to two runloops in order to get setTimeouts to
|
||||
* fire during scrolling. (`NSDefaultRunLoopMode` and `UITrackingRunLoopMode`)
|
||||
* TODO: We can invent a `requestAnimationFrame` and
|
||||
* `requestAvailableAnimationFrame` to control if callbacks can be fired during
|
||||
* an animation.
|
||||
* http://stackoverflow.com/questions/12622800/why-does-uiscrollview-pause-my-cadisplaylink
|
||||
*
|
||||
*/
|
||||
- (instancetype)initWithBridge:(RCTBridge *)bridge
|
||||
{
|
||||
if ((self = [super init])) {
|
||||
|
@ -61,92 +29,86 @@
|
|||
|
||||
- (void)loadBundleAtURL:(NSURL *)scriptURL onComplete:(void (^)(NSError *))onComplete
|
||||
{
|
||||
if (scriptURL == nil) {
|
||||
// Sanitize the script URL
|
||||
scriptURL = [RCTConvert NSURL:scriptURL.absoluteString];
|
||||
|
||||
if (!scriptURL ||
|
||||
([scriptURL isFileURL] && ![[NSFileManager defaultManager] fileExistsAtPath:scriptURL.path])) {
|
||||
NSError *error = [NSError errorWithDomain:@"JavaScriptLoader" code:1 userInfo:@{
|
||||
NSLocalizedDescriptionKey: @"No script URL provided"
|
||||
NSLocalizedDescriptionKey: scriptURL ? [NSString stringWithFormat:@"Script at '%@' could not be found.", scriptURL] : @"No script URL provided"
|
||||
}];
|
||||
onComplete(error);
|
||||
return;
|
||||
}
|
||||
|
||||
if ([scriptURL isFileURL]) {
|
||||
NSString *bundlePath = [[NSBundle bundleForClass:[self class]] resourcePath];
|
||||
NSString *localPath = [scriptURL.absoluteString substringFromIndex:@"file://".length];
|
||||
|
||||
if (![localPath hasPrefix:bundlePath]) {
|
||||
NSString *absolutePath = [NSString stringWithFormat:@"%@/%@", bundlePath, localPath];
|
||||
scriptURL = [NSURL fileURLWithPath:absolutePath];
|
||||
}
|
||||
}
|
||||
|
||||
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:scriptURL completionHandler:
|
||||
^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
|
||||
// Handle general request errors
|
||||
if (error) {
|
||||
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
|
||||
NSString *desc = [@"Could not connect to development server. Ensure node server is running and available on the same network - run 'npm start' from react-native root\n\nURL: " stringByAppendingString:[scriptURL absoluteString]];
|
||||
NSDictionary *userInfo = @{
|
||||
NSLocalizedDescriptionKey: desc,
|
||||
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
|
||||
NSUnderlyingErrorKey: error,
|
||||
};
|
||||
error = [NSError errorWithDomain:@"JSServer"
|
||||
code:error.code
|
||||
userInfo:userInfo];
|
||||
}
|
||||
onComplete(error);
|
||||
return;
|
||||
}
|
||||
// Handle general request errors
|
||||
if (error) {
|
||||
if ([[error domain] isEqualToString:NSURLErrorDomain]) {
|
||||
NSString *desc = [@"Could not connect to development server. Ensure node server is running and available on the same network - run 'npm start' from react-native root\n\nURL: " stringByAppendingString:[scriptURL absoluteString]];
|
||||
NSDictionary *userInfo = @{
|
||||
NSLocalizedDescriptionKey: desc,
|
||||
NSLocalizedFailureReasonErrorKey: [error localizedDescription],
|
||||
NSUnderlyingErrorKey: error,
|
||||
};
|
||||
error = [NSError errorWithDomain:@"JSServer"
|
||||
code:error.code
|
||||
userInfo:userInfo];
|
||||
}
|
||||
onComplete(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse response as text
|
||||
NSStringEncoding encoding = NSUTF8StringEncoding;
|
||||
if (response.textEncodingName != nil) {
|
||||
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
||||
if (cfEncoding != kCFStringEncodingInvalidId) {
|
||||
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
|
||||
}
|
||||
}
|
||||
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
|
||||
// Parse response as text
|
||||
NSStringEncoding encoding = NSUTF8StringEncoding;
|
||||
if (response.textEncodingName != nil) {
|
||||
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)response.textEncodingName);
|
||||
if (cfEncoding != kCFStringEncodingInvalidId) {
|
||||
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
|
||||
}
|
||||
}
|
||||
NSString *rawText = [[NSString alloc] initWithData:data encoding:encoding];
|
||||
|
||||
// Handle HTTP errors
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
|
||||
NSDictionary *userInfo;
|
||||
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
|
||||
if ([errorDetails isKindOfClass:[NSDictionary class]] &&
|
||||
[errorDetails[@"errors"] isKindOfClass:[NSArray class]]) {
|
||||
NSMutableArray *fakeStack = [[NSMutableArray alloc] init];
|
||||
for (NSDictionary *err in errorDetails[@"errors"]) {
|
||||
[fakeStack addObject: @{
|
||||
@"methodName": err[@"description"] ?: @"",
|
||||
@"file": err[@"filename"] ?: @"",
|
||||
@"lineNumber": err[@"lineNumber"] ?: @0
|
||||
}];
|
||||
}
|
||||
userInfo = @{
|
||||
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
|
||||
@"stack": fakeStack,
|
||||
};
|
||||
} else {
|
||||
userInfo = @{NSLocalizedDescriptionKey: rawText};
|
||||
}
|
||||
error = [NSError errorWithDomain:@"JSServer"
|
||||
code:[(NSHTTPURLResponse *)response statusCode]
|
||||
userInfo:userInfo];
|
||||
// Handle HTTP errors
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]] && [(NSHTTPURLResponse *)response statusCode] != 200) {
|
||||
NSDictionary *userInfo;
|
||||
NSDictionary *errorDetails = RCTJSONParse(rawText, nil);
|
||||
if ([errorDetails isKindOfClass:[NSDictionary class]] &&
|
||||
[errorDetails[@"errors"] isKindOfClass:[NSArray class]]) {
|
||||
NSMutableArray *fakeStack = [[NSMutableArray alloc] init];
|
||||
for (NSDictionary *err in errorDetails[@"errors"]) {
|
||||
[fakeStack addObject: @{
|
||||
@"methodName": err[@"description"] ?: @"",
|
||||
@"file": err[@"filename"] ?: @"",
|
||||
@"lineNumber": err[@"lineNumber"] ?: @0
|
||||
}];
|
||||
}
|
||||
userInfo = @{
|
||||
NSLocalizedDescriptionKey: errorDetails[@"message"] ?: @"No message provided",
|
||||
@"stack": fakeStack,
|
||||
};
|
||||
} else {
|
||||
userInfo = @{NSLocalizedDescriptionKey: rawText};
|
||||
}
|
||||
error = [NSError errorWithDomain:@"JSServer"
|
||||
code:[(NSHTTPURLResponse *)response statusCode]
|
||||
userInfo:userInfo];
|
||||
|
||||
onComplete(error);
|
||||
return;
|
||||
}
|
||||
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
|
||||
sourceCodeModule.scriptURL = scriptURL;
|
||||
sourceCodeModule.scriptText = rawText;
|
||||
onComplete(error);
|
||||
return;
|
||||
}
|
||||
RCTSourceCode *sourceCodeModule = _bridge.modules[RCTBridgeModuleNameForClass([RCTSourceCode class])];
|
||||
sourceCodeModule.scriptURL = scriptURL;
|
||||
sourceCodeModule.scriptText = rawText;
|
||||
|
||||
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
onComplete(scriptError);
|
||||
});
|
||||
}];
|
||||
}];
|
||||
[_bridge enqueueApplicationScript:rawText url:scriptURL onComplete:^(NSError *scriptError) {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
onComplete(scriptError);
|
||||
});
|
||||
}];
|
||||
}];
|
||||
|
||||
[task resume];
|
||||
}
|
||||
|
|
|
@ -23,16 +23,6 @@
|
|||
#import "RCTWebViewExecutor.h"
|
||||
#import "UIView+React.h"
|
||||
|
||||
/**
|
||||
* HACK(t6568049) This should be removed soon, hiding to prevent people from
|
||||
* relying on it
|
||||
*/
|
||||
@interface RCTBridge (RCTRootView)
|
||||
|
||||
- (void)setJavaScriptExecutor:(id<RCTJavaScriptExecutor>)executor;
|
||||
|
||||
@end
|
||||
|
||||
@interface RCTUIManager (RCTRootView)
|
||||
|
||||
- (NSNumber *)allocateRootTag;
|
||||
|
@ -120,11 +110,11 @@ RCT_IMPORT_METHOD(ReactIOS, unmountComponentAtNodeAndRemoveContainer)
|
|||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
|
||||
/**
|
||||
* Every root view that is created must have a unique react tag.
|
||||
* Every root view that is created must have a unique React tag.
|
||||
* Numbering of these tags goes from 1, 11, 21, 31, etc
|
||||
*
|
||||
* NOTE: Since the bridge persists, the RootViews might be reused, so now
|
||||
* the react tag is assigned every time we load new content.
|
||||
* the React tag is assigned every time we load new content.
|
||||
*/
|
||||
[_contentView removeFromSuperview];
|
||||
_contentView = [[UIView alloc] initWithFrame:self.bounds];
|
||||
|
|