diff --git a/packages/fiber/src/index.tsx b/packages/fiber/src/index.tsx index 72efc6bd33..4b8af9106f 100644 --- a/packages/fiber/src/index.tsx +++ b/packages/fiber/src/index.tsx @@ -16,6 +16,6 @@ export type { ThreeEvent, Events, EventManager, ComputeFunction } from './core/e export { createEvents } from './core/events' export type { ObjectMap, Camera } from './core/utils' export * from './web/Canvas' -export { createPointerEvents as events } from './web/events' +export { createPointerEvents as events, createPointerEvents } from './web/events' export type { GlobalRenderCallback, GlobalEffectType } from './core/loop' export * from './core' diff --git a/packages/fiber/src/native.tsx b/packages/fiber/src/native.tsx index c37809041f..97a3a1ddc1 100644 --- a/packages/fiber/src/native.tsx +++ b/packages/fiber/src/native.tsx @@ -17,6 +17,7 @@ export { createEvents } from './core/events' export type { ObjectMap, Camera } from './core/utils' export * from './native/Canvas' export { createTouchEvents as events } from './native/events' +export { createPointerEvents } from './web/events' export type { GlobalRenderCallback, GlobalEffectType } from './core/loop' export * from './core' diff --git a/packages/fiber/src/native/Canvas.tsx b/packages/fiber/src/native/Canvas.tsx index db14e5034b..b39f5437a3 100644 --- a/packages/fiber/src/native/Canvas.tsx +++ b/packages/fiber/src/native/Canvas.tsx @@ -1,11 +1,21 @@ import * as React from 'react' import * as THREE from 'three' -import { View, ViewProps, ViewStyle, LayoutChangeEvent, StyleSheet, PixelRatio } from 'react-native' +import { + View, + type ViewProps, + type ViewStyle, + type GestureResponderHandlers, + type GestureResponderEvent, + PanResponder, + type LayoutChangeEvent, + StyleSheet, + PixelRatio, +} from 'react-native' import { ExpoWebGLRenderingContext, GLView } from 'expo-gl' import { useContextBridge, FiberProvider } from 'its-fine' import { SetBlock, Block, ErrorBoundary, useMutableCallback } from '../core/utils' import { extend, createRoot, unmountComponentAtNode, RenderProps, ReconcilerRoot } from '../core' -import { createTouchEvents } from './events' +import { createPointerEvents } from '../web/events' import { RootState, Size } from '../core/store' export interface CanvasProps extends Omit, 'size' | 'dpr'>, ViewProps { @@ -25,7 +35,7 @@ const CanvasImpl = /*#__PURE__*/ React.forwardRef( children, style, gl, - events = createTouchEvents, + events = createPointerEvents, shadows, linear, flat, @@ -51,7 +61,7 @@ const CanvasImpl = /*#__PURE__*/ React.forwardRef( const [{ width, height, top, left }, setSize] = React.useState({ width: 0, height: 0, top: 0, left: 0 }) const [canvas, setCanvas] = React.useState(null) - const [bind, setBind] = React.useState() + const [bind, setBind] = React.useState() React.useImperativeHandle(forwardedRef, () => viewRef.current) const handlePointerMissed = useMutableCallback(onPointerMissed) @@ -76,21 +86,92 @@ const CanvasImpl = /*#__PURE__*/ React.forwardRef( // Called on context create or swap // https://github.com/pmndrs/react-three-fiber/pull/2297 const onContextCreate = React.useCallback((context: ExpoWebGLRenderingContext) => { - const canvasShim = { + const listeners = new Map() + + const canvas = { + style: {}, width: context.drawingBufferWidth, height: context.drawingBufferHeight, - style: {}, - addEventListener: (() => {}) as any, - removeEventListener: (() => {}) as any, + clientWidth: context.drawingBufferWidth, clientHeight: context.drawingBufferHeight, - getContext: ((_: any, { antialias = false }) => { + getContext: (_: any, { antialias = false }) => { setAntialias(antialias) return context - }) as any, - } as HTMLCanvasElement + }, + addEventListener(type: string, listener: EventListener) { + let callbacks = listeners.get(type) + if (!callbacks) { + callbacks = [] + listeners.set(type, callbacks) + } + + callbacks.push(listener) + }, + removeEventListener(type: string, listener: EventListener) { + const callbacks = listeners.get(type) + if (callbacks) { + const index = callbacks.indexOf(listener) + if (index !== -1) callbacks.splice(index, 1) + } + }, + dispatchEvent(event: Event) { + Object.assign(event, { target: this }) + + const callbacks = listeners.get(event.type) + if (callbacks) { + for (const callback of callbacks) { + callback(event) + } + } + }, + setPointerCapture() { + // TODO + }, + releasePointerCapture() { + // TODO + }, + } as unknown as HTMLCanvasElement + + // TODO: this is wrong but necessary to trick controls + // @ts-ignore + canvas.ownerDocument = canvas + canvas.getRootNode = () => canvas + + root.current = createRoot(canvas) + setCanvas(canvas) + + function handleTouch(gestureEvent: GestureResponderEvent, type: string): true { + gestureEvent.persist() + + canvas.dispatchEvent( + Object.assign(gestureEvent.nativeEvent, { + type, + offsetX: gestureEvent.nativeEvent.locationX, + offsetY: gestureEvent.nativeEvent.locationY, + pointerType: 'touch', + }) as unknown as Event, + ) - root.current = createRoot(canvasShim) - setCanvas(canvasShim) + return true + } + + const responder = PanResponder.create({ + onStartShouldSetPanResponder: () => true, + onMoveShouldSetPanResponder: () => true, + onMoveShouldSetPanResponderCapture: () => true, + onPanResponderTerminationRequest: () => true, + onStartShouldSetPanResponderCapture: (e) => handleTouch(e, 'pointercapture'), + onPanResponderStart: (e) => handleTouch(e, 'pointerdown'), + onPanResponderMove: (e) => handleTouch(e, 'pointermove'), + onPanResponderEnd: (e, state) => { + handleTouch(e, 'pointerup') + if (Math.hypot(state.dx, state.dy) < 20) handleTouch(e, 'click') + }, + onPanResponderRelease: (e) => handleTouch(e, 'pointerleave'), + onPanResponderTerminate: (e) => handleTouch(e, 'lostpointercapture'), + onPanResponderReject: (e) => handleTouch(e, 'lostpointercapture'), + }) + setBind(responder.panHandlers) }, []) if (root.current && width > 0 && height > 0) { @@ -115,9 +196,6 @@ const CanvasImpl = /*#__PURE__*/ React.forwardRef( onPointerMissed: (...args) => handlePointerMissed.current?.(...args), // Overwrite onCreated to apply RN bindings onCreated: (state: RootState) => { - // Bind events after creation - setBind(state.events.handlers) - // Bind render to RN bridge const context = state.gl.getContext() as ExpoWebGLRenderingContext const renderFrame = state.gl.render.bind(state.gl) @@ -145,10 +223,17 @@ const CanvasImpl = /*#__PURE__*/ React.forwardRef( }, [canvas]) return ( - - {width > 0 && ( - - )} + + + {width > 0 && ( + + )} + ) },