import * as React from 'react' ; import { PixelRatio , ViewProps , ViewStyle , View , StyleSheet } from 'react-native' ; import { ExpoWebGLRenderingContext , GLView } from 'expo-gl' ; import { useIsomorphicLayoutEffect } from '../shared/hooks' ; import { SetBlock , ErrorBoundary , Block } from '../shared/components' ; import { createInternals } from '../shared/utils' ; import { filterKeys } from '../utils' ; import { events } from './events' ; import { RESERVED_PROPS } from '../constants' ; import { RenderProps , RootState } from '../types' ; export type GLContext = ExpoWebGLRenderingContext | WebGLRenderingContext ; export interface Props extends Omit < RenderProps , 'dpr' | 'size' > , ViewProps { children : React . ReactNode ; style ? : ViewStyle ; } /**
 * A list of custom Canvas props.
 */ export const CANVAS_PROPS = [ 'renderer' , 'camera' , 'orthographic' , 'frameloop' , 'events' , 'onCreated' ] as const ; /**
 * A resizeable canvas whose children are declarative OGL elements.
 */ export const Canvas = React . forwardRef < View , Props > ( ( { children , style , ... rest } , forwardedRef ) => { const internalProps = filterKeys ( rest , false , ... CANVAS_PROPS ) ; const viewProps = filterKeys ( rest , true , ... CANVAS_PROPS , ... RESERVED_PROPS ) ; const internalState = React . useRef < RootState > ( ) ; const [ { width , height } , setSize ] = React . useState ( { width : 0 , height : 0 } ) ; const [ context , setContext ] = React . useState < GLContext | null > ( ) ; const [ bind , setBind ] = React . useState < any > ( ) ; const [ block , setBlock ] = React . useState < SetBlock > ( false ) ; const [ error , setError ] = React . useState < any > ( false ) ; if ( block ) throw block ; if ( error ) throw error ; useIsomorphicLayoutEffect ( ( ) => { if ( ! context ) return ; if ( ! internalState . current ) { const canvas = { width : context . drawingBufferWidth , height : context . drawingBufferHeight , style : { } , addEventListener : ( ( ) => { } ) as any , removeEventListener : ( ( ) => { } ) as any , clientHeight : context . drawingBufferHeight , } as HTMLCanvasElement ; ( context . canvas as any ) = canvas ; ( canvas . getContext as any ) = ( ) => context ; internalState . current = createInternals ( canvas , { events , ... internalProps , } ) ; } if ( width > 0 && height > 0 ) { const state = internalState . current ; state . renderer . dpr = PixelRatio . get ( ) ; state . renderer . setSize ( width , height ) ; const projection = internalProps . orthographic ? 'orthographic' : 'perspective' ; state . camera [ projection ] ( { aspect : width / height } ) ; state . root . render ( < ErrorBoundary set = { setError } > < React . Suspense fallback = { < Block set = { setBlock } / > } > { children } < /React.Suspense> < /ErrorBoundary>); if ( ! bind ) setBind ( state . events . connected ) ; } } , [ width , height , context , internalProps , children ] ) ; React . useEffect ( ( ) => { const state = internalState . current ; return ( ) => state ?. root . unmount ( ) ; } , [ ] ) ; return ( < View { ... viewProps } ref = { forwardedRef } onLayout = { ( event ) => { const { width , height } = event . nativeEvent . layout ; setSize ( { width , height } ) ; } } style = { { flex : 1 , ... style } } { ... bind } > { width > 0 && < GLView onContextCreate = { setContext } style = { StyleSheet . absoluteFill } / > } < /View>); } ) ;