@hxa-rn/react-native-gcanvas 6.0.24-beta.1 → 6.0.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/CHANGELOG.md +2 -2
  2. package/LICENSE +203 -21
  3. package/README.md +207 -207
  4. package/harmony/gcanvas/LICENSE +203 -21
  5. package/harmony/gcanvas/NOTICE +32 -32
  6. package/harmony/gcanvas/OAT.xml +37 -37
  7. package/harmony/gcanvas/build-profile.json5 +16 -16
  8. package/harmony/gcanvas/hvigorfile.ts +1 -1
  9. package/harmony/gcanvas/index.ets +1 -1
  10. package/harmony/gcanvas/oh-package.json5 +14 -14
  11. package/harmony/gcanvas/src/main/cpp/CMakeLists.txt +126 -126
  12. package/harmony/gcanvas/src/main/cpp/GCanvasNapi.cpp +485 -485
  13. package/harmony/gcanvas/src/main/cpp/GCanvasXComponent.cpp +106 -106
  14. package/harmony/gcanvas/src/main/cpp/GcanvasPackage.h +24 -24
  15. package/harmony/gcanvas/src/main/ets/GCanvasModuleTurboModule.ts +448 -448
  16. package/harmony/gcanvas/src/main/ets/GCanvasNativeEngine.ts +206 -206
  17. package/harmony/gcanvas/src/main/ets/components/GCanvasView.ets +64 -64
  18. package/harmony/gcanvas/src/main/module.json5 +21 -21
  19. package/harmony/gcanvas/src/main/resources/base/element/string.json +12 -12
  20. package/harmony/gcanvas/src/main/resources/en_US/element/string.json +8 -8
  21. package/harmony/gcanvas/src/main/resources/zh_CN/element/string.json +12 -12
  22. package/harmony/gcanvas.har +0 -0
  23. package/package.json +4 -4
  24. package/packages/gcanvas/.babelrc +4 -4
  25. package/packages/gcanvas/.eslintrc.js +133 -133
  26. package/packages/gcanvas/package.json +19 -19
  27. package/packages/gcanvas/rollup.config.js +13 -13
  28. package/packages/gcanvas/src/bridge/react-native.js +286 -286
  29. package/packages/gcanvas/src/bridge/weex.js +210 -210
  30. package/packages/gcanvas/src/context/2d/FillStyleLinearGradient.js +16 -16
  31. package/packages/gcanvas/src/context/2d/FillStylePattern.js +7 -7
  32. package/packages/gcanvas/src/context/2d/FillStyleRadialGradient.js +16 -16
  33. package/packages/gcanvas/src/context/2d/OffScreenRenderingContext.js +48 -48
  34. package/packages/gcanvas/src/context/2d/RenderingContext.js +787 -787
  35. package/packages/gcanvas/src/context/webgl/ActiveInfo.js +10 -10
  36. package/packages/gcanvas/src/context/webgl/Buffer.js +20 -20
  37. package/packages/gcanvas/src/context/webgl/Framebuffer.js +20 -20
  38. package/packages/gcanvas/src/context/webgl/GLenum.js +300 -300
  39. package/packages/gcanvas/src/context/webgl/GLmethod.js +141 -141
  40. package/packages/gcanvas/src/context/webgl/GLtype.js +22 -22
  41. package/packages/gcanvas/src/context/webgl/Program.js +20 -20
  42. package/packages/gcanvas/src/context/webgl/Renderbuffer.js +20 -20
  43. package/packages/gcanvas/src/context/webgl/RenderingContext.js +1479 -1479
  44. package/packages/gcanvas/src/context/webgl/Shader.js +21 -21
  45. package/packages/gcanvas/src/context/webgl/ShaderPrecisionFormat.js +10 -10
  46. package/packages/gcanvas/src/context/webgl/Texture.js +21 -21
  47. package/packages/gcanvas/src/context/webgl/UniformLocation.js +21 -21
  48. package/packages/gcanvas/src/context/webgl/classUtils.js +2 -2
  49. package/packages/gcanvas/src/env/canvas.js +210 -210
  50. package/packages/gcanvas/src/env/image.js +76 -76
  51. package/packages/gcanvas/src/env/off-screen-canvas.js +74 -74
  52. package/packages/gcanvas/src/index.js +56 -56
  53. package/src/components/CanvasView.js +20 -20
  54. package/src/components/GCanvasComponent.js +408 -408
  55. package/src/index.js +69 -69
  56. package/src/packages/gcanvas/.babelrc +4 -4
  57. package/src/packages/gcanvas/.eslintrc.js +133 -133
  58. package/src/packages/gcanvas/package.json +19 -19
  59. package/src/packages/gcanvas/rollup.config.js +13 -13
  60. package/src/packages/gcanvas/src/bridge/react-native.js +288 -288
  61. package/src/packages/gcanvas/src/bridge/weex.js +210 -210
  62. package/src/packages/gcanvas/src/context/2d/FillStyleLinearGradient.js +16 -16
  63. package/src/packages/gcanvas/src/context/2d/FillStylePattern.js +7 -7
  64. package/src/packages/gcanvas/src/context/2d/FillStyleRadialGradient.js +16 -16
  65. package/src/packages/gcanvas/src/context/2d/OffScreenRenderingContext.js +48 -48
  66. package/src/packages/gcanvas/src/context/2d/RenderingContext.js +801 -801
  67. package/src/packages/gcanvas/src/context/webgl/ActiveInfo.js +10 -10
  68. package/src/packages/gcanvas/src/context/webgl/Buffer.js +20 -20
  69. package/src/packages/gcanvas/src/context/webgl/Framebuffer.js +20 -20
  70. package/src/packages/gcanvas/src/context/webgl/GLenum.js +300 -300
  71. package/src/packages/gcanvas/src/context/webgl/GLmethod.js +141 -141
  72. package/src/packages/gcanvas/src/context/webgl/GLtype.js +22 -22
  73. package/src/packages/gcanvas/src/context/webgl/Program.js +20 -20
  74. package/src/packages/gcanvas/src/context/webgl/Renderbuffer.js +20 -20
  75. package/src/packages/gcanvas/src/context/webgl/RenderingContext.js +1479 -1479
  76. package/src/packages/gcanvas/src/context/webgl/Shader.js +21 -21
  77. package/src/packages/gcanvas/src/context/webgl/ShaderPrecisionFormat.js +10 -10
  78. package/src/packages/gcanvas/src/context/webgl/Texture.js +21 -21
  79. package/src/packages/gcanvas/src/context/webgl/UniformLocation.js +21 -21
  80. package/src/packages/gcanvas/src/context/webgl/classUtils.js +2 -2
  81. package/src/packages/gcanvas/src/env/canvas.js +210 -210
  82. package/src/packages/gcanvas/src/env/image.js +76 -76
  83. package/src/packages/gcanvas/src/env/off-screen-canvas.js +74 -74
  84. package/src/packages/gcanvas/src/index.js +56 -56
  85. package/src/specs/v1/GCanvasViewNativeComponent.ts +42 -42
  86. package/src/specs/v1/NativeGCanvasModule.ts +140 -140
@@ -1,408 +1,408 @@
1
- import React, {Component} from 'react';
2
- // import PropTypes from 'prop-types';
3
- import {
4
- NativeEventEmitter,
5
- NativeModules,
6
- PanResponder,
7
- PixelRatio,
8
- Platform,
9
- Text,
10
- View,
11
- findNodeHandle,
12
- } from 'react-native';
13
- import '@flyskywhy/react-native-browser-polyfill';
14
- import CanvasView from './CanvasView';
15
- import {enable, disable, ReactNativeBridge} from '../packages/gcanvas';
16
- import GCanvasModule from '../specs/v1/NativeGCanvasModule';
17
- ReactNativeBridge.GCanvasModule = GCanvasModule;
18
- ReactNativeBridge.Platform = Platform;
19
-
20
- export default class GCanvasView extends Component {
21
- constructor(props) {
22
- super(props);
23
- this.refCanvasView = null;
24
- this.canvas = null;
25
- this.panScale = 1;
26
- this.touchBank = [];
27
- this.canvasViewPageX = 0;
28
- this.canvasViewPageY = 0;
29
- this.pressedIds = new Set();
30
-
31
- let panResponder = PanResponder.create({
32
- onStartShouldSetPanResponder: () => true,
33
- onMoveShouldSetPanResponder: () => false,
34
- onPanResponderGrant: (event) => {
35
- // as [PanResponder is not working when there are multiple touches](https://github.com/facebook/react-native/issues/8094)
36
- // said, maybe it's a bug of react-native, and found (tested on Android) 2nd finger touch will
37
- // not invoke onPanResponderGrant(), so be the work around this.touchBank
38
-
39
- // even componentWillUnmount() inovked then componentDidMount() again, the
40
- // event.touchHistory.touchBank still maintain before componentWillUnmount(),
41
- // maybe it's a bug of react-native, so be the workaround here
42
- this.touchBank = event.touchHistory.touchBank.filter(touch => !!touch).map(touch => {return {...touch, touchActive: false}});
43
- // 原逻辑只在 Move 里补 mousedown;轻点不滑动就没有按下。Grant 时先派发。
44
- this.nativeEventToTouches(event).forEach((touch) => this.emitPointer('mousedown', touch));
45
- },
46
- onPanResponderMove: (event, gestureState) => {
47
- // tested multiple touches with babylonjs demo `Drag and drop` in https://github.com/flyskywhy/GCanvasRNExamples
48
- let touchBank = event.touchHistory.touchBank.filter(touch => !!touch);
49
-
50
- let toDownTouchs = touchBank.filter(cur => {
51
- let preFound = this.touchBank.find(pre => this.touch2PointerId(pre) === this.touch2PointerId(cur));
52
- if (preFound) {
53
- if (!preFound.touchActive && cur.touchActive) {
54
- // new finger touch can be here because `touchActive: false` workaround in onPanResponderGrant()
55
- return true;
56
- } else {
57
- // componentWillUnmount() then componentDidMount() again cause
58
- // event.touchHistory.touchBank still maintain before componentWillUnmount() will
59
- // preFound.touchActive === false and cur.touchActive === false
60
- // to be here
61
- return false;
62
- }
63
- } else {
64
- // 2nd finger touch will be here as 2nd finger touch will not invoke onPanResponderGrant() to change this.touchBank
65
- return true;
66
- }
67
- });
68
- let toUpTouchs = this.touchBank.filter(pre => touchBank.filter(cur => this.touch2PointerId(cur) === this.touch2PointerId(pre) && !cur.touchActive).length && pre.touchActive);
69
- let toMoveTouchs = touchBank.filter(cur => {
70
- if (cur.touchActive) {
71
- if (toDownTouchs.filter(down => this.touch2PointerId(down) === this.touch2PointerId(cur)).length) {
72
- // let 'down' -> 'up' that without 'move' in middle can be possible, thus present same behavior with Web
73
- return false;
74
- } else {
75
- return true;
76
- }
77
- }
78
- });
79
-
80
- // need ...touch here otherwise this.touchBank will change automatically with touchBank
81
- this.touchBank = touchBank.map(touch => {return {...touch}});
82
-
83
- toDownTouchs.map(touch => this.emitPointer('mousedown', touch));
84
- toUpTouchs.map(touch => this.emitPointer('mouseup', touch));
85
- toMoveTouchs.map(touch => this.emitPointer('mousemove', touch));
86
- },
87
- onPanResponderRelease: (event, gestureState) => {
88
- // found 2nd finger touch release will not invoke onPanResponderRelease(), so be the work around this.touchBank with toUpTouchs above
89
- this.touchBank.map(touch => {
90
- if (touch.touchActive) {
91
- // actually only last finger touch release will touch.touchActive === true
92
- this.emitPointer('mouseup', touch);
93
- touch.touchActive = false;
94
- }
95
- });
96
- this.nativeEventToTouches(event).forEach((touch) => this.emitPointer('mouseup', touch));
97
- },
98
- onPanResponderTerminationRequest: () => false,
99
- onPanResponderTerminate: () => false,
100
- });
101
-
102
- this.state = {
103
- panResponder: {},
104
- };
105
-
106
- if (props.isGestureResponsible) {
107
- this.state.panResponder = panResponder;
108
- }
109
- }
110
-
111
- static propTypes = {
112
- // isOffscreen: PropTypes.bool,
113
- // NOTE: View.propTypes 已在 React 16+ / RN 0.71+ 移除(含 ViewPropTypes),
114
- // 在鸿蒙 RN 基座加载即抛 TypeError 白屏,故仅保留自定义 prop 类型注释。
115
- };
116
-
117
- static defaultProps = {
118
- // Indicate whether response for gesture inside canvas,
119
- // so that PanResponder outside GCanvasView can be useable
120
- // when isGestureResponsible is false.
121
- // Default is true, so that zdog can be "mousemove".
122
- isGestureResponsible: true,
123
- // only affect 2d
124
- // on Web, putImageData looks like will auto clearRect before,
125
- // but I think it's more convenient to not clearRect before
126
- // putImageData when imageData has some transparent pixels,
127
- // so the default value is false, and if you want to be exactly
128
- // compatible with Web, you can set it to true
129
- isAutoClearRectBeforePutImageData: false,
130
- // With canvas 2d, generally be true, so that can resolve issue
131
- // [Canvas resize](https://github.com/flyskywhy/react-native-gcanvas/issues/32).
132
- // If canvas 2d also have ctx call loop like usage of 'zdog', also can be true.
133
- // With webgl 3d, generally be false, so that can
134
- // [let webgl 3d can continue gl call loop without restart it by drawSome() after rotate screenOrientation]
135
- // (https://github.com/flyskywhy/GCanvasRNExamples/commit/3cd2f3f).
136
- isResetGlViewportAfterSetWidthOrHeight: true,
137
- // devicePixelRatio default is undefined and means default is PixelRatio.get() ,
138
- // ref to "About devicePixelRatio" in README.md
139
- devicePixelRatio: undefined,
140
- // Indicate whether this canvas can be use by document.createElement('canvas') (as offscreen canvas),
141
- // ref to README.md for more usage attentions like `zIndex: -100` in style
142
- offscreenCanvas: false,
143
- // only affect webgl
144
- // false: use AutoSwap, means gcanvas use a setInterval(render, 16) to exec cached cmds
145
- // to generate and display graphics
146
- // true: not use AutoSwap, means APP use it's own loop to call gl.clear(), thus gcanvas
147
- // will exec cached cmds to generate and display graphics, then add gl.clear to
148
- // cmds cache to be exec next time, and also offer APP a canvas._swapBuffers() if
149
- // APP want exec cached cmds to generate and display graphics manually
150
- disableAutoSwap: false,
151
- };
152
-
153
- nativeEventToTouches = (evt) => {
154
- const ne = (evt && evt.nativeEvent) || {};
155
- const list = (ne.changedTouches && ne.changedTouches.length)
156
- ? ne.changedTouches
157
- : (ne.touches && ne.touches.length)
158
- ? ne.touches
159
- : [ne];
160
- return list.filter(Boolean).map((t, i) => {
161
- const locationX = t.locationX ?? ne.locationX;
162
- const locationY = t.locationY ?? ne.locationY;
163
- const pageX = t.pageX ?? ne.pageX ??
164
- (locationX != null ? locationX + this.canvasViewPageX : 0);
165
- const pageY = t.pageY ?? ne.pageY ??
166
- (locationY != null ? locationY + this.canvasViewPageY : 0);
167
- return {
168
- currentPageX: pageX,
169
- currentPageY: pageY,
170
- startPageX: t.startPageX ?? pageX,
171
- startPageY: t.startPageY ?? pageY,
172
- startTimeStamp: t.identifier ?? t.startTimeStamp ?? i,
173
- currentTimeStamp: t.timestamp ?? ne.timestamp ?? Date.now(),
174
- touchActive: true,
175
- };
176
- });
177
- };
178
-
179
- emitPointer = (type, touch) => {
180
- const id = this.touch2PointerId(touch);
181
- if (type === 'mousedown') {
182
- if (this.pressedIds.has(id)) {
183
- return;
184
- }
185
- this.pressedIds.add(id);
186
- } else if (type === 'mouseup') {
187
- if (!this.pressedIds.has(id) && this.pressedIds.size === 0) {
188
- // 仍派发抬起:鸿蒙上 Grant/Start 可能丢了,用户至少能看到 mouseup
189
- }
190
- this.pressedIds.delete(id);
191
- }
192
- this.dispatch({touch, type});
193
- };
194
-
195
- onHarmonyTouchStart = (evt) => {
196
- this.nativeEventToTouches(evt).forEach((touch) => this.emitPointer('mousedown', touch));
197
- };
198
-
199
- onHarmonyTouchMove = (evt) => {
200
- this.nativeEventToTouches(evt).forEach((touch) => this.emitPointer('mousemove', touch));
201
- };
202
-
203
- onHarmonyTouchEnd = (evt) => {
204
- this.nativeEventToTouches(evt).forEach((touch) => this.emitPointer('mouseup', touch));
205
- };
206
-
207
- dispatch = ({touch, type}) => {
208
- let pointerEvent = this.touch2PointerEvent({touch, type});
209
- if (this.canvas) {
210
- this.canvas.dispatchEvent(pointerEvent);
211
- }
212
-
213
- // as `node_modules/zdog/js/dragger.js` use window.addEventListener not element.addEventListener on mousemove
214
- window.dispatchEvent(pointerEvent);
215
-
216
- switch (type) {
217
- case 'mousemove':
218
- this.props.onMouseMove && this.props.onMouseMove(pointerEvent);
219
- break;
220
- case 'mousedown':
221
- this.props.onMouseDown && this.props.onMouseDown(pointerEvent);
222
- break;
223
- case 'mouseup':
224
- this.props.onMouseUp && this.props.onMouseUp(pointerEvent);
225
- break;
226
- default:
227
- break;
228
- }
229
- };
230
-
231
- touch2PointerEvent = ({touch, type}) => {
232
- return {
233
- altKey: false,
234
- button: type === 'mousemove' ? -1 : 0,
235
- buttons: type === 'mousemove' ? 0 : 1,
236
- clientX: (touch.currentPageX - this.canvasViewPageX) * this.panScale,
237
- clientY: (touch.currentPageY - this.canvasViewPageY) * this.panScale,
238
- ctrlKey: false,
239
- isTrusted: true,
240
- metaKey: false,
241
- pageX: touch.currentPageX * this.panScale,
242
- pageY: touch.currentPageY * this.panScale,
243
- pointerId: this.touch2PointerId(touch),
244
- pointerType: "touch",
245
- shiftKey: false,
246
- target: this.canvas,
247
- timeStamp: touch.currentTimeStamp,
248
- type, // to works with babylonjs, type should not be 'pointerdown', 'pointermove' or 'pointerup'
249
- };
250
- };
251
-
252
- touch2PointerId = (touch) => touch.startTimeStamp + touch.startPageX + touch.startPageY;
253
-
254
- _onIsReady = (event) => {
255
- if (this.props.onIsReady) {
256
- this.props.onIsReady(
257
- Platform.OS === 'ios' ? true : event.nativeEvent.value,
258
- );
259
- }
260
- };
261
-
262
- _onLayout = (event) => {
263
- let width = event.nativeEvent.layout.width | 0; // width is fixed not float just like Web
264
- let height = event.nativeEvent.layout.height | 0;
265
- let ref = '' + findNodeHandle(this.refCanvasView);
266
-
267
- this.refCanvasView && this.refCanvasView.measure((x, y, width, height, pageX, pageY) => {
268
- this.canvasViewPageX = pageX;
269
- this.canvasViewPageY = pageY;
270
- });
271
-
272
- // When onLayout is invoked again (e.g. change phone orientation), if assign
273
- // `this.canvas` again, that also means `this` in dispatchEvent() of
274
- // `event-target-shim/dist/event-target-shim.js` changed, thus dispatchEvent()
275
- // can do nothing and cause `node_modules/zdog/js/dragger.js` can't be moved
276
- // by finger anymore.
277
- // So let `this.canvas` be assigned here only once.
278
- if (this.canvas !== null) {
279
- if (this.canvas.clientWidth !== width || this.canvas.clientHeight !== height) {
280
- this.canvas.clientWidth = width;
281
- this.canvas.clientHeight = height;
282
- if (this.props.onCanvasResize) {
283
- // APP can `this.canvas.width = width` in onCanvasResize()
284
- this.props.onCanvasResize({width, height, canvas: this.canvas});
285
- }
286
- }
287
- return;
288
- }
289
-
290
- if (this.refCanvasView === null) {
291
- this._onLayout(event);
292
- return;
293
- }
294
-
295
- this.canvas = enable(
296
- {
297
- ref,
298
- style: {
299
- width,
300
- height,
301
- },
302
- },
303
- {
304
- isAutoClearRectBeforePutImageData: this.props.isAutoClearRectBeforePutImageData,
305
- isResetGlViewportAfterSetWidthOrHeight: this.props.isResetGlViewportAfterSetWidthOrHeight,
306
- devicePixelRatio: this.props.devicePixelRatio,
307
- disableAutoSwap: this.props.disableAutoSwap,
308
- bridge: ReactNativeBridge,
309
- },
310
- );
311
-
312
- this.panScale = PixelRatio.get() / this.canvas._devicePixelRatio;
313
-
314
- if (this.props.offscreenCanvas && !global.createCanvasElements.includes(this.canvas)) {
315
- global.createCanvasElements.push(this.canvas);
316
- if (global.createCanvasElementsObj) {
317
- // ios release (RN0.71.6 JSC) createCanvasElements.push(canvas) in a class but still
318
- // get [] means createCanvasElements.length is 0 in another class, so have to
319
- // use createCanvasElementsObj below, and reserve createCanvasElements for compatible
320
- global.createCanvasElementsObj[this.canvas.id] = this.canvas;
321
- }
322
- }
323
-
324
- if (this.props.onCanvasCreate) {
325
- this.props.onCanvasCreate(this.canvas);
326
- }
327
- };
328
-
329
- componentDidMount() {
330
- // on iOS, sometimes setLogLevel(0) will cause APP stuck if running in Xcode (because too many logs?), but setLogLevel(0)
331
- // will not cause APP stuck if not running in damn Xcode, tested in https://github.com/flyskywhy/snakeRN
332
- // ReactNativeBridge.GCanvasModule.setLogLevel(0); // 0 means DEBUG
333
-
334
- // since https://github.com/flyskywhy/react-native-gcanvas/issues/44 said latest RN
335
- // will warning `EventEmitter.removeListener ... Method has been deprecated` and
336
- // only iOS use EventEmitter and on iOS always true in _onIsReady(), so just comment below
337
- // if (Platform.OS === 'ios') {
338
- // // while always true in _onIsReady(), here is just to suppress warning
339
- // // on iOS Sending `GCanvasReady` with no listeners registered.
340
- // const emitter = new NativeEventEmitter(ReactNativeBridge.GCanvasModule);
341
- // emitter.addListener('GCanvasReady', this._onIsReady);
342
- // }
343
- }
344
-
345
- componentWillUnmount() {
346
- if (this.canvas !== null) {
347
- let index = global.createCanvasElements.findIndex(canvas => canvas === this.canvas);
348
- if (index >= 0) {
349
- global.createCanvasElements.splice(index, 1);
350
- }
351
-
352
- if (global.createCanvasElementsObj && global.createCanvasElementsObj[this.canvas.id]) {
353
- delete global.createCanvasElementsObj[this.canvas.id];
354
- }
355
-
356
- disable(this.canvas);
357
- }
358
-
359
- // if (Platform.OS === 'ios') {
360
- // const emitter = new NativeEventEmitter(ReactNativeBridge.GCanvasModule);
361
- // emitter.removeListener('GCanvasReady', this._onIsReady);
362
- // }
363
- }
364
-
365
- render() {
366
- if (Platform.OS === 'web') {
367
- return (
368
- <View {...this.props}>
369
- <Text>{'Please use <canvas> not <CanvasView> on Web'}</Text>
370
- </View>
371
- );
372
- } else if (Platform.OS === 'harmony' && this.props.isGestureResponsible) {
373
- // XComponent SURFACE 常只把 move 交给 RN PanResponder,按下/抬起丢失。
374
- // 外包 RN View 收 onTouch*;同时独占 responder,避免外层 ScrollView 竖滑抢走后触发 cancel→mouseup。
375
- return (
376
- <View
377
- style={this.props.style}
378
- collapsable={false}
379
- onStartShouldSetResponder={() => true}
380
- onMoveShouldSetResponder={() => true}
381
- onResponderTerminationRequest={() => false}
382
- onTouchStart={this.onHarmonyTouchStart}
383
- onTouchMove={this.onHarmonyTouchMove}
384
- onTouchEnd={this.onHarmonyTouchEnd}
385
- onTouchCancel={this.onHarmonyTouchEnd}>
386
- <CanvasView
387
- {...this.props}
388
- style={{width: '100%', height: '100%'}}
389
- pointerEvents="none"
390
- ref={(view) => (this.refCanvasView = view)}
391
- onLayout={this._onLayout}
392
- onChange={this._onIsReady}
393
- />
394
- </View>
395
- );
396
- } else {
397
- return (
398
- <CanvasView
399
- {...this.props}
400
- {...this.state.panResponder.panHandlers}
401
- ref={(view) => (this.refCanvasView = view)}
402
- onLayout={this._onLayout}
403
- onChange={this._onIsReady}
404
- />
405
- );
406
- }
407
- }
408
- }
1
+ import React, {Component} from 'react';
2
+ // import PropTypes from 'prop-types';
3
+ import {
4
+ NativeEventEmitter,
5
+ NativeModules,
6
+ PanResponder,
7
+ PixelRatio,
8
+ Platform,
9
+ Text,
10
+ View,
11
+ findNodeHandle,
12
+ } from 'react-native';
13
+ import '@flyskywhy/react-native-browser-polyfill';
14
+ import CanvasView from './CanvasView';
15
+ import {enable, disable, ReactNativeBridge} from '../packages/gcanvas';
16
+ import GCanvasModule from '../specs/v1/NativeGCanvasModule';
17
+ ReactNativeBridge.GCanvasModule = GCanvasModule;
18
+ ReactNativeBridge.Platform = Platform;
19
+
20
+ export default class GCanvasView extends Component {
21
+ constructor(props) {
22
+ super(props);
23
+ this.refCanvasView = null;
24
+ this.canvas = null;
25
+ this.panScale = 1;
26
+ this.touchBank = [];
27
+ this.canvasViewPageX = 0;
28
+ this.canvasViewPageY = 0;
29
+ this.pressedIds = new Set();
30
+
31
+ let panResponder = PanResponder.create({
32
+ onStartShouldSetPanResponder: () => true,
33
+ onMoveShouldSetPanResponder: () => false,
34
+ onPanResponderGrant: (event) => {
35
+ // as [PanResponder is not working when there are multiple touches](https://github.com/facebook/react-native/issues/8094)
36
+ // said, maybe it's a bug of react-native, and found (tested on Android) 2nd finger touch will
37
+ // not invoke onPanResponderGrant(), so be the work around this.touchBank
38
+
39
+ // even componentWillUnmount() inovked then componentDidMount() again, the
40
+ // event.touchHistory.touchBank still maintain before componentWillUnmount(),
41
+ // maybe it's a bug of react-native, so be the workaround here
42
+ this.touchBank = event.touchHistory.touchBank.filter(touch => !!touch).map(touch => {return {...touch, touchActive: false}});
43
+ // 原逻辑只在 Move 里补 mousedown;轻点不滑动就没有按下。Grant 时先派发。
44
+ this.nativeEventToTouches(event).forEach((touch) => this.emitPointer('mousedown', touch));
45
+ },
46
+ onPanResponderMove: (event, gestureState) => {
47
+ // tested multiple touches with babylonjs demo `Drag and drop` in https://github.com/flyskywhy/GCanvasRNExamples
48
+ let touchBank = event.touchHistory.touchBank.filter(touch => !!touch);
49
+
50
+ let toDownTouchs = touchBank.filter(cur => {
51
+ let preFound = this.touchBank.find(pre => this.touch2PointerId(pre) === this.touch2PointerId(cur));
52
+ if (preFound) {
53
+ if (!preFound.touchActive && cur.touchActive) {
54
+ // new finger touch can be here because `touchActive: false` workaround in onPanResponderGrant()
55
+ return true;
56
+ } else {
57
+ // componentWillUnmount() then componentDidMount() again cause
58
+ // event.touchHistory.touchBank still maintain before componentWillUnmount() will
59
+ // preFound.touchActive === false and cur.touchActive === false
60
+ // to be here
61
+ return false;
62
+ }
63
+ } else {
64
+ // 2nd finger touch will be here as 2nd finger touch will not invoke onPanResponderGrant() to change this.touchBank
65
+ return true;
66
+ }
67
+ });
68
+ let toUpTouchs = this.touchBank.filter(pre => touchBank.filter(cur => this.touch2PointerId(cur) === this.touch2PointerId(pre) && !cur.touchActive).length && pre.touchActive);
69
+ let toMoveTouchs = touchBank.filter(cur => {
70
+ if (cur.touchActive) {
71
+ if (toDownTouchs.filter(down => this.touch2PointerId(down) === this.touch2PointerId(cur)).length) {
72
+ // let 'down' -> 'up' that without 'move' in middle can be possible, thus present same behavior with Web
73
+ return false;
74
+ } else {
75
+ return true;
76
+ }
77
+ }
78
+ });
79
+
80
+ // need ...touch here otherwise this.touchBank will change automatically with touchBank
81
+ this.touchBank = touchBank.map(touch => {return {...touch}});
82
+
83
+ toDownTouchs.map(touch => this.emitPointer('mousedown', touch));
84
+ toUpTouchs.map(touch => this.emitPointer('mouseup', touch));
85
+ toMoveTouchs.map(touch => this.emitPointer('mousemove', touch));
86
+ },
87
+ onPanResponderRelease: (event, gestureState) => {
88
+ // found 2nd finger touch release will not invoke onPanResponderRelease(), so be the work around this.touchBank with toUpTouchs above
89
+ this.touchBank.map(touch => {
90
+ if (touch.touchActive) {
91
+ // actually only last finger touch release will touch.touchActive === true
92
+ this.emitPointer('mouseup', touch);
93
+ touch.touchActive = false;
94
+ }
95
+ });
96
+ this.nativeEventToTouches(event).forEach((touch) => this.emitPointer('mouseup', touch));
97
+ },
98
+ onPanResponderTerminationRequest: () => false,
99
+ onPanResponderTerminate: () => false,
100
+ });
101
+
102
+ this.state = {
103
+ panResponder: {},
104
+ };
105
+
106
+ if (props.isGestureResponsible) {
107
+ this.state.panResponder = panResponder;
108
+ }
109
+ }
110
+
111
+ static propTypes = {
112
+ // isOffscreen: PropTypes.bool,
113
+ // NOTE: View.propTypes 已在 React 16+ / RN 0.71+ 移除(含 ViewPropTypes),
114
+ // 在鸿蒙 RN 基座加载即抛 TypeError 白屏,故仅保留自定义 prop 类型注释。
115
+ };
116
+
117
+ static defaultProps = {
118
+ // Indicate whether response for gesture inside canvas,
119
+ // so that PanResponder outside GCanvasView can be useable
120
+ // when isGestureResponsible is false.
121
+ // Default is true, so that zdog can be "mousemove".
122
+ isGestureResponsible: true,
123
+ // only affect 2d
124
+ // on Web, putImageData looks like will auto clearRect before,
125
+ // but I think it's more convenient to not clearRect before
126
+ // putImageData when imageData has some transparent pixels,
127
+ // so the default value is false, and if you want to be exactly
128
+ // compatible with Web, you can set it to true
129
+ isAutoClearRectBeforePutImageData: false,
130
+ // With canvas 2d, generally be true, so that can resolve issue
131
+ // [Canvas resize](https://github.com/flyskywhy/react-native-gcanvas/issues/32).
132
+ // If canvas 2d also have ctx call loop like usage of 'zdog', also can be true.
133
+ // With webgl 3d, generally be false, so that can
134
+ // [let webgl 3d can continue gl call loop without restart it by drawSome() after rotate screenOrientation]
135
+ // (https://github.com/flyskywhy/GCanvasRNExamples/commit/3cd2f3f).
136
+ isResetGlViewportAfterSetWidthOrHeight: true,
137
+ // devicePixelRatio default is undefined and means default is PixelRatio.get() ,
138
+ // ref to "About devicePixelRatio" in README.md
139
+ devicePixelRatio: undefined,
140
+ // Indicate whether this canvas can be use by document.createElement('canvas') (as offscreen canvas),
141
+ // ref to README.md for more usage attentions like `zIndex: -100` in style
142
+ offscreenCanvas: false,
143
+ // only affect webgl
144
+ // false: use AutoSwap, means gcanvas use a setInterval(render, 16) to exec cached cmds
145
+ // to generate and display graphics
146
+ // true: not use AutoSwap, means APP use it's own loop to call gl.clear(), thus gcanvas
147
+ // will exec cached cmds to generate and display graphics, then add gl.clear to
148
+ // cmds cache to be exec next time, and also offer APP a canvas._swapBuffers() if
149
+ // APP want exec cached cmds to generate and display graphics manually
150
+ disableAutoSwap: false,
151
+ };
152
+
153
+ nativeEventToTouches = (evt) => {
154
+ const ne = (evt && evt.nativeEvent) || {};
155
+ const list = (ne.changedTouches && ne.changedTouches.length)
156
+ ? ne.changedTouches
157
+ : (ne.touches && ne.touches.length)
158
+ ? ne.touches
159
+ : [ne];
160
+ return list.filter(Boolean).map((t, i) => {
161
+ const locationX = t.locationX ?? ne.locationX;
162
+ const locationY = t.locationY ?? ne.locationY;
163
+ const pageX = t.pageX ?? ne.pageX ??
164
+ (locationX != null ? locationX + this.canvasViewPageX : 0);
165
+ const pageY = t.pageY ?? ne.pageY ??
166
+ (locationY != null ? locationY + this.canvasViewPageY : 0);
167
+ return {
168
+ currentPageX: pageX,
169
+ currentPageY: pageY,
170
+ startPageX: t.startPageX ?? pageX,
171
+ startPageY: t.startPageY ?? pageY,
172
+ startTimeStamp: t.identifier ?? t.startTimeStamp ?? i,
173
+ currentTimeStamp: t.timestamp ?? ne.timestamp ?? Date.now(),
174
+ touchActive: true,
175
+ };
176
+ });
177
+ };
178
+
179
+ emitPointer = (type, touch) => {
180
+ const id = this.touch2PointerId(touch);
181
+ if (type === 'mousedown') {
182
+ if (this.pressedIds.has(id)) {
183
+ return;
184
+ }
185
+ this.pressedIds.add(id);
186
+ } else if (type === 'mouseup') {
187
+ if (!this.pressedIds.has(id) && this.pressedIds.size === 0) {
188
+ // 仍派发抬起:鸿蒙上 Grant/Start 可能丢了,用户至少能看到 mouseup
189
+ }
190
+ this.pressedIds.delete(id);
191
+ }
192
+ this.dispatch({touch, type});
193
+ };
194
+
195
+ onHarmonyTouchStart = (evt) => {
196
+ this.nativeEventToTouches(evt).forEach((touch) => this.emitPointer('mousedown', touch));
197
+ };
198
+
199
+ onHarmonyTouchMove = (evt) => {
200
+ this.nativeEventToTouches(evt).forEach((touch) => this.emitPointer('mousemove', touch));
201
+ };
202
+
203
+ onHarmonyTouchEnd = (evt) => {
204
+ this.nativeEventToTouches(evt).forEach((touch) => this.emitPointer('mouseup', touch));
205
+ };
206
+
207
+ dispatch = ({touch, type}) => {
208
+ let pointerEvent = this.touch2PointerEvent({touch, type});
209
+ if (this.canvas) {
210
+ this.canvas.dispatchEvent(pointerEvent);
211
+ }
212
+
213
+ // as `node_modules/zdog/js/dragger.js` use window.addEventListener not element.addEventListener on mousemove
214
+ window.dispatchEvent(pointerEvent);
215
+
216
+ switch (type) {
217
+ case 'mousemove':
218
+ this.props.onMouseMove && this.props.onMouseMove(pointerEvent);
219
+ break;
220
+ case 'mousedown':
221
+ this.props.onMouseDown && this.props.onMouseDown(pointerEvent);
222
+ break;
223
+ case 'mouseup':
224
+ this.props.onMouseUp && this.props.onMouseUp(pointerEvent);
225
+ break;
226
+ default:
227
+ break;
228
+ }
229
+ };
230
+
231
+ touch2PointerEvent = ({touch, type}) => {
232
+ return {
233
+ altKey: false,
234
+ button: type === 'mousemove' ? -1 : 0,
235
+ buttons: type === 'mousemove' ? 0 : 1,
236
+ clientX: (touch.currentPageX - this.canvasViewPageX) * this.panScale,
237
+ clientY: (touch.currentPageY - this.canvasViewPageY) * this.panScale,
238
+ ctrlKey: false,
239
+ isTrusted: true,
240
+ metaKey: false,
241
+ pageX: touch.currentPageX * this.panScale,
242
+ pageY: touch.currentPageY * this.panScale,
243
+ pointerId: this.touch2PointerId(touch),
244
+ pointerType: "touch",
245
+ shiftKey: false,
246
+ target: this.canvas,
247
+ timeStamp: touch.currentTimeStamp,
248
+ type, // to works with babylonjs, type should not be 'pointerdown', 'pointermove' or 'pointerup'
249
+ };
250
+ };
251
+
252
+ touch2PointerId = (touch) => touch.startTimeStamp + touch.startPageX + touch.startPageY;
253
+
254
+ _onIsReady = (event) => {
255
+ if (this.props.onIsReady) {
256
+ this.props.onIsReady(
257
+ Platform.OS === 'ios' ? true : event.nativeEvent.value,
258
+ );
259
+ }
260
+ };
261
+
262
+ _onLayout = (event) => {
263
+ let width = event.nativeEvent.layout.width | 0; // width is fixed not float just like Web
264
+ let height = event.nativeEvent.layout.height | 0;
265
+ let ref = '' + findNodeHandle(this.refCanvasView);
266
+
267
+ this.refCanvasView && this.refCanvasView.measure((x, y, width, height, pageX, pageY) => {
268
+ this.canvasViewPageX = pageX;
269
+ this.canvasViewPageY = pageY;
270
+ });
271
+
272
+ // When onLayout is invoked again (e.g. change phone orientation), if assign
273
+ // `this.canvas` again, that also means `this` in dispatchEvent() of
274
+ // `event-target-shim/dist/event-target-shim.js` changed, thus dispatchEvent()
275
+ // can do nothing and cause `node_modules/zdog/js/dragger.js` can't be moved
276
+ // by finger anymore.
277
+ // So let `this.canvas` be assigned here only once.
278
+ if (this.canvas !== null) {
279
+ if (this.canvas.clientWidth !== width || this.canvas.clientHeight !== height) {
280
+ this.canvas.clientWidth = width;
281
+ this.canvas.clientHeight = height;
282
+ if (this.props.onCanvasResize) {
283
+ // APP can `this.canvas.width = width` in onCanvasResize()
284
+ this.props.onCanvasResize({width, height, canvas: this.canvas});
285
+ }
286
+ }
287
+ return;
288
+ }
289
+
290
+ if (this.refCanvasView === null) {
291
+ this._onLayout(event);
292
+ return;
293
+ }
294
+
295
+ this.canvas = enable(
296
+ {
297
+ ref,
298
+ style: {
299
+ width,
300
+ height,
301
+ },
302
+ },
303
+ {
304
+ isAutoClearRectBeforePutImageData: this.props.isAutoClearRectBeforePutImageData,
305
+ isResetGlViewportAfterSetWidthOrHeight: this.props.isResetGlViewportAfterSetWidthOrHeight,
306
+ devicePixelRatio: this.props.devicePixelRatio,
307
+ disableAutoSwap: this.props.disableAutoSwap,
308
+ bridge: ReactNativeBridge,
309
+ },
310
+ );
311
+
312
+ this.panScale = PixelRatio.get() / this.canvas._devicePixelRatio;
313
+
314
+ if (this.props.offscreenCanvas && !global.createCanvasElements.includes(this.canvas)) {
315
+ global.createCanvasElements.push(this.canvas);
316
+ if (global.createCanvasElementsObj) {
317
+ // ios release (RN0.71.6 JSC) createCanvasElements.push(canvas) in a class but still
318
+ // get [] means createCanvasElements.length is 0 in another class, so have to
319
+ // use createCanvasElementsObj below, and reserve createCanvasElements for compatible
320
+ global.createCanvasElementsObj[this.canvas.id] = this.canvas;
321
+ }
322
+ }
323
+
324
+ if (this.props.onCanvasCreate) {
325
+ this.props.onCanvasCreate(this.canvas);
326
+ }
327
+ };
328
+
329
+ componentDidMount() {
330
+ // on iOS, sometimes setLogLevel(0) will cause APP stuck if running in Xcode (because too many logs?), but setLogLevel(0)
331
+ // will not cause APP stuck if not running in damn Xcode, tested in https://github.com/flyskywhy/snakeRN
332
+ // ReactNativeBridge.GCanvasModule.setLogLevel(0); // 0 means DEBUG
333
+
334
+ // since https://github.com/flyskywhy/react-native-gcanvas/issues/44 said latest RN
335
+ // will warning `EventEmitter.removeListener ... Method has been deprecated` and
336
+ // only iOS use EventEmitter and on iOS always true in _onIsReady(), so just comment below
337
+ // if (Platform.OS === 'ios') {
338
+ // // while always true in _onIsReady(), here is just to suppress warning
339
+ // // on iOS Sending `GCanvasReady` with no listeners registered.
340
+ // const emitter = new NativeEventEmitter(ReactNativeBridge.GCanvasModule);
341
+ // emitter.addListener('GCanvasReady', this._onIsReady);
342
+ // }
343
+ }
344
+
345
+ componentWillUnmount() {
346
+ if (this.canvas !== null) {
347
+ let index = global.createCanvasElements.findIndex(canvas => canvas === this.canvas);
348
+ if (index >= 0) {
349
+ global.createCanvasElements.splice(index, 1);
350
+ }
351
+
352
+ if (global.createCanvasElementsObj && global.createCanvasElementsObj[this.canvas.id]) {
353
+ delete global.createCanvasElementsObj[this.canvas.id];
354
+ }
355
+
356
+ disable(this.canvas);
357
+ }
358
+
359
+ // if (Platform.OS === 'ios') {
360
+ // const emitter = new NativeEventEmitter(ReactNativeBridge.GCanvasModule);
361
+ // emitter.removeListener('GCanvasReady', this._onIsReady);
362
+ // }
363
+ }
364
+
365
+ render() {
366
+ if (Platform.OS === 'web') {
367
+ return (
368
+ <View {...this.props}>
369
+ <Text>{'Please use <canvas> not <CanvasView> on Web'}</Text>
370
+ </View>
371
+ );
372
+ } else if (Platform.OS === 'harmony' && this.props.isGestureResponsible) {
373
+ // XComponent SURFACE 常只把 move 交给 RN PanResponder,按下/抬起丢失。
374
+ // 外包 RN View 收 onTouch*;同时独占 responder,避免外层 ScrollView 竖滑抢走后触发 cancel→mouseup。
375
+ return (
376
+ <View
377
+ style={this.props.style}
378
+ collapsable={false}
379
+ onStartShouldSetResponder={() => true}
380
+ onMoveShouldSetResponder={() => true}
381
+ onResponderTerminationRequest={() => false}
382
+ onTouchStart={this.onHarmonyTouchStart}
383
+ onTouchMove={this.onHarmonyTouchMove}
384
+ onTouchEnd={this.onHarmonyTouchEnd}
385
+ onTouchCancel={this.onHarmonyTouchEnd}>
386
+ <CanvasView
387
+ {...this.props}
388
+ style={{width: '100%', height: '100%'}}
389
+ pointerEvents="none"
390
+ ref={(view) => (this.refCanvasView = view)}
391
+ onLayout={this._onLayout}
392
+ onChange={this._onIsReady}
393
+ />
394
+ </View>
395
+ );
396
+ } else {
397
+ return (
398
+ <CanvasView
399
+ {...this.props}
400
+ {...this.state.panResponder.panHandlers}
401
+ ref={(view) => (this.refCanvasView = view)}
402
+ onLayout={this._onLayout}
403
+ onChange={this._onIsReady}
404
+ />
405
+ );
406
+ }
407
+ }
408
+ }