Skip to content

Commit

Permalink
feat(native): implement native EventTarget methods (#3252)
Browse files Browse the repository at this point in the history
  • Loading branch information
CodyJasonBennett committed Aug 10, 2024
1 parent 3802b53 commit 4ec3cb0
Show file tree
Hide file tree
Showing 3 changed files with 107 additions and 21 deletions.
2 changes: 1 addition & 1 deletion packages/fiber/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
1 change: 1 addition & 0 deletions packages/fiber/src/native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
125 changes: 105 additions & 20 deletions packages/fiber/src/native/Canvas.tsx
Original file line number Diff line number Diff line change
@@ -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<RenderProps<HTMLCanvasElement>, 'size' | 'dpr'>, ViewProps {
Expand All @@ -25,7 +35,7 @@ const CanvasImpl = /*#__PURE__*/ React.forwardRef<View, Props>(
children,
style,
gl,
events = createTouchEvents,
events = createPointerEvents,
shadows,
linear,
flat,
Expand All @@ -51,7 +61,7 @@ const CanvasImpl = /*#__PURE__*/ React.forwardRef<View, Props>(

const [{ width, height, top, left }, setSize] = React.useState<Size>({ width: 0, height: 0, top: 0, left: 0 })
const [canvas, setCanvas] = React.useState<HTMLCanvasElement | null>(null)
const [bind, setBind] = React.useState<any>()
const [bind, setBind] = React.useState<GestureResponderHandlers>()
React.useImperativeHandle(forwardedRef, () => viewRef.current)

const handlePointerMissed = useMutableCallback(onPointerMissed)
Expand All @@ -76,21 +86,92 @@ const CanvasImpl = /*#__PURE__*/ React.forwardRef<View, Props>(
// 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<string, EventListener[]>()

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<HTMLCanvasElement>(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<HTMLCanvasElement>(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) {
Expand All @@ -115,9 +196,6 @@ const CanvasImpl = /*#__PURE__*/ React.forwardRef<View, Props>(
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)
Expand Down Expand Up @@ -145,10 +223,17 @@ const CanvasImpl = /*#__PURE__*/ React.forwardRef<View, Props>(
}, [canvas])

return (
<View {...props} ref={viewRef} onLayout={onLayout} style={{ flex: 1, ...style }} {...bind}>
{width > 0 && (
<GLView msaaSamples={antialias ? 4 : 0} onContextCreate={onContextCreate} style={StyleSheet.absoluteFill} />
)}
<View ref={viewRef} onLayout={onLayout} style={{ flex: 1, ...style }}>
<View {...props} style={{ flex: 1 }}>
{width > 0 && (
<GLView
{...bind}
msaaSamples={antialias ? 4 : 0}
onContextCreate={onContextCreate}
style={StyleSheet.absoluteFill}
/>
)}
</View>
</View>
)
},
Expand Down

0 comments on commit 4ec3cb0

Please sign in to comment.