1 | import invariant from 'invariant';
|
2 | import PropTypes from 'prop-types';
|
3 | import React from 'react';
|
4 | import { StyleSheet } from 'react-native';
|
5 | import { UnavailabilityError, CodedError } from '@unimodules/core';
|
6 | function getImageForAsset(asset) {
|
7 | if (asset != null && typeof asset === 'object' && asset !== null && asset.downloadAsync) {
|
8 | const dataURI = asset.localUri || asset.uri || '';
|
9 | const image = new Image();
|
10 | image.src = dataURI;
|
11 | return image;
|
12 | }
|
13 | return asset;
|
14 | }
|
15 | function asExpoContext(gl) {
|
16 | gl.endFrameEXP = function glEndFrameEXP() { };
|
17 | if (!gl['_expo_texImage2D']) {
|
18 | gl['_expo_texImage2D'] = gl.texImage2D;
|
19 | gl.texImage2D = (...props) => {
|
20 | let nextProps = [...props];
|
21 | nextProps.push(getImageForAsset(nextProps.pop()));
|
22 | return gl['_expo_texImage2D'](...nextProps);
|
23 | };
|
24 | }
|
25 | if (!gl['_expo_texSubImage2D']) {
|
26 | gl['_expo_texSubImage2D'] = gl.texSubImage2D;
|
27 | gl.texSubImage2D = (...props) => {
|
28 | let nextProps = [...props];
|
29 | nextProps.push(getImageForAsset(nextProps.pop()));
|
30 | return gl['_expo_texSubImage2D'](...nextProps);
|
31 | };
|
32 | }
|
33 | return gl;
|
34 | }
|
35 | function ensureContext(canvas, contextAttributes) {
|
36 | if (!canvas) {
|
37 | throw new CodedError('ERR_GL_INVALID', 'Attempting to use the GL context before it has been created.');
|
38 | }
|
39 | const context = canvas.getContext('webgl', contextAttributes) ||
|
40 | canvas.getContext('webgl-experimental', contextAttributes) ||
|
41 | canvas.getContext('experimental-webgl', contextAttributes);
|
42 | invariant(context, 'Browser does not support WebGL');
|
43 | return asExpoContext(context);
|
44 | }
|
45 | function stripNonDOMProps(props) {
|
46 | for (let k in propTypes) {
|
47 | if (k in props) {
|
48 | delete props[k];
|
49 | }
|
50 | }
|
51 | return props;
|
52 | }
|
53 | const propTypes = {
|
54 | onContextCreate: PropTypes.func.isRequired,
|
55 | onContextRestored: PropTypes.func,
|
56 | onContextLost: PropTypes.func,
|
57 | webglContextAttributes: PropTypes.object,
|
58 | };
|
59 | export class GLView extends React.Component {
|
60 | constructor() {
|
61 | super(...arguments);
|
62 | this.state = {
|
63 | width: 0,
|
64 | height: 0,
|
65 | };
|
66 | this._hasContextBeenCreated = false;
|
67 | this._contextCreated = () => {
|
68 | this.gl = this._createContext();
|
69 | this.props.onContextCreate(this.gl);
|
70 | if (this.canvas) {
|
71 | this.canvas.addEventListener('webglcontextlost', this._contextLost);
|
72 | this.canvas.addEventListener('webglcontextrestored', this._contextRestored);
|
73 | }
|
74 | };
|
75 | this._updateLayout = () => {
|
76 | if (this.container) {
|
77 | const { clientWidth: width = 0, clientHeight: height = 0 } = this.container;
|
78 | this.setState({ width, height });
|
79 | }
|
80 | };
|
81 | this._contextLost = (event) => {
|
82 | event.preventDefault();
|
83 | this.gl = undefined;
|
84 | if (this.props.onContextLost) {
|
85 | this.props.onContextLost();
|
86 | }
|
87 | };
|
88 | this._contextRestored = () => {
|
89 | if (this.props.onContextRestored) {
|
90 | this.gl = this._createContext();
|
91 | this.props.onContextRestored(this.gl);
|
92 | }
|
93 | };
|
94 | this._assignCanvasRef = (canvas) => {
|
95 | this.canvas = canvas;
|
96 | };
|
97 | this._assignContainerRef = (element) => {
|
98 | if (element) {
|
99 | this.container = element;
|
100 | }
|
101 | else {
|
102 | this.container = undefined;
|
103 | }
|
104 | this._updateLayout();
|
105 | };
|
106 | }
|
107 | static async createContextAsync() {
|
108 | const canvas = document.createElement('canvas');
|
109 | canvas.width = window.innerWidth * window.devicePixelRatio;
|
110 | canvas.height = window.innerHeight * window.devicePixelRatio;
|
111 | return ensureContext(canvas);
|
112 | }
|
113 | static async destroyContextAsync(exgl) {
|
114 |
|
115 | return true;
|
116 | }
|
117 | static async takeSnapshotAsync(exgl, options = {}) {
|
118 | invariant(exgl, 'GLView.takeSnapshotAsync(): canvas is not defined');
|
119 | const canvas = exgl.canvas;
|
120 | return await new Promise(resolve => {
|
121 | canvas.toBlob((blob) => {
|
122 |
|
123 | resolve({
|
124 | uri: blob,
|
125 | localUri: '',
|
126 | width: canvas.width,
|
127 | height: canvas.height,
|
128 | });
|
129 | }, options.format, options.compress);
|
130 | });
|
131 | }
|
132 | componentDidMount() {
|
133 | if (window.addEventListener) {
|
134 | window.addEventListener('resize', this._updateLayout);
|
135 | }
|
136 | }
|
137 | componentWillUnmount() {
|
138 | if (this.gl) {
|
139 | const loseContextExt = this.gl.getExtension('WEBGL_lose_context');
|
140 | if (loseContextExt) {
|
141 | loseContextExt.loseContext();
|
142 | }
|
143 | this.gl = undefined;
|
144 | }
|
145 | if (this.canvas) {
|
146 | this.canvas.removeEventListener('webglcontextlost', this._contextLost);
|
147 | this.canvas.removeEventListener('webglcontextrestored', this._contextRestored);
|
148 | }
|
149 | window.removeEventListener('resize', this._updateLayout);
|
150 | }
|
151 | render() {
|
152 | const { devicePixelRatio = 1 } = window;
|
153 | const { style, ...props } = this.props;
|
154 | const { width, height } = this.state;
|
155 | const domProps = stripNonDOMProps(props);
|
156 | const containerStyle = StyleSheet.flatten([{ flex: 1 }, style]);
|
157 | return (<div ref={this._assignContainerRef} style={containerStyle}>
|
158 | <canvas ref={this._assignCanvasRef} style={{ flex: 1, width, height }} width={width * devicePixelRatio} height={height * devicePixelRatio} {...domProps}/>
|
159 | </div>);
|
160 | }
|
161 | componentDidUpdate() {
|
162 | if (this.canvas && !this._hasContextBeenCreated) {
|
163 | this._hasContextBeenCreated = true;
|
164 | this._contextCreated();
|
165 | }
|
166 | }
|
167 | _createContext() {
|
168 | const { webglContextAttributes } = this.props;
|
169 | const gl = ensureContext(this.canvas, webglContextAttributes);
|
170 | this._webglContextAttributes = webglContextAttributes || {};
|
171 | return gl;
|
172 | }
|
173 | _getGlOrReject() {
|
174 | if (!this.gl) {
|
175 | throw new CodedError('ERR_GL_INVALID', 'Attempting to use the GL context before it has been created.');
|
176 | }
|
177 | return this.gl;
|
178 | }
|
179 | async takeSnapshotAsync(options = {}) {
|
180 | if (!GLView.takeSnapshotAsync) {
|
181 | throw new UnavailabilityError('expo-gl', 'takeSnapshotAsync');
|
182 | }
|
183 | const gl = this._getGlOrReject();
|
184 | return await GLView.takeSnapshotAsync(gl, options);
|
185 | }
|
186 | async startARSessionAsync() {
|
187 | throw new UnavailabilityError('GLView', 'startARSessionAsync');
|
188 | }
|
189 | async createCameraTextureAsync() {
|
190 | throw new UnavailabilityError('GLView', 'createCameraTextureAsync');
|
191 | }
|
192 | async destroyObjectAsync(glObject) {
|
193 | throw new UnavailabilityError('GLView', 'destroyObjectAsync');
|
194 | }
|
195 | }
|
196 | GLView.propTypes = propTypes;
|
197 | //# sourceMappingURL=GLView.web.js.map |
\ | No newline at end of file |