From d787c10f5e2fd712b538f4c8f63bd543698c2533 Mon Sep 17 00:00:00 2001 From: Cody Bennett <23324155+CodyJasonBennett@users.noreply.github.com> Date: Sat, 4 Jun 2022 01:56:33 -0500 Subject: [PATCH] feat: add size render prop --- src/Canvas.native.tsx | 40 +++++++-------------------------------- src/Canvas.tsx | 44 ++++++++----------------------------------- src/renderer.tsx | 39 +++++++++++++++++++++++++++++++++++++- src/types.ts | 13 +++++++++++++ 4 files changed, 66 insertions(+), 70 deletions(-) diff --git a/src/Canvas.native.tsx b/src/Canvas.native.tsx index b4e5539..ac1feff 100644 --- a/src/Canvas.native.tsx +++ b/src/Canvas.native.tsx @@ -9,11 +9,9 @@ import '@expo/browser-polyfill' export type GLContext = ExpoWebGLRenderingContext | WebGLRenderingContext -export interface CanvasProps extends Omit, ViewProps { +export interface CanvasProps extends Omit, ViewProps { children: React.ReactNode style?: ViewStyle - orthographic?: boolean - frameloop?: 'always' | 'never' } /** @@ -54,15 +52,18 @@ export const Canvas = React.forwardRef(function Canvas( setCanvas(canvasShim) }, []) + // Render to screen if (canvas && width > 0 && height > 0) { - // Render to screen - const state = render( + render( }>{children} , canvas, { + size: { width, height }, mode, + orthographic, + frameloop, renderer, // expo-gl can only render at native dpr/resolution // https://github.com/expo/expo-three/issues/39 @@ -80,40 +81,13 @@ export const Canvas = React.forwardRef(function Canvas( } } - // Animate - const animate = (time?: number) => { - // Cancel animation if frameloop is set, otherwise keep looping - if (frameloop === 'never') return cancelAnimationFrame(state.animation) - state.animation = requestAnimationFrame(animate) - - // Call subscribed elements - for (const ref of state.subscribed) ref.current?.(state, time) - - // If rendering manually, skip render - if (state.priority) return - - // Render to screen - state.renderer.render({ scene: state.scene, camera: state.camera }) - } - if (frameloop !== 'never') animate() - // Bind events setBind(state.events?.connected) return onCreated?.(state) }, }, - ).getState() - - // Handle resize - if (state.renderer.width !== width || state.renderer.height !== height) { - // Set dpr, handle resize - state.renderer.setSize(width, height) - - // Update projection - const projection = orthographic ? 'orthographic' : 'perspective' - state.camera[projection]({ aspect: width / height }) - } + ) } // Cleanup on unmount diff --git a/src/Canvas.tsx b/src/Canvas.tsx index 80c509b..e9624bb 100644 --- a/src/Canvas.tsx +++ b/src/Canvas.tsx @@ -7,11 +7,9 @@ import { events as createPointerEvents } from './events' import { RenderProps, SetBlock } from './types' import { render, unmountComponentAtNode } from './renderer' -export interface CanvasProps extends RenderProps, React.HTMLAttributes { +export interface CanvasProps extends Omit, React.HTMLAttributes { children: React.ReactNode resize?: ResizeOptions - orthographic?: boolean - frameloop?: 'always' | 'never' } /** @@ -50,51 +48,25 @@ export const Canvas = React.forwardRef(function // Throw exception outwards if anything within Canvas throws if (error) throw error + // Render to screen if (canvas && width > 0 && height > 0) { - // Render to screen - const state = render( + render( }>{children} , canvas, { + size: { width, height }, mode, + orthographic, + frameloop, renderer, dpr, camera, events, - onCreated(state) { - // Animate - const animate = (time?: number) => { - // Cancel animation if frameloop is set, otherwise keep looping - if (frameloop === 'never') return cancelAnimationFrame(state.animation) - state.animation = requestAnimationFrame(animate) - - // Call subscribed elements - for (const ref of state.subscribed) ref.current?.(state, time) - - // If rendering manually, skip render - if (state.priority) return - - // Render to screen - state.renderer.render({ scene: state.scene, camera: state.camera }) - } - if (frameloop !== 'never') animate() - - return onCreated?.(state) - }, + onCreated, }, - ).getState() - - // Handle resize - if (state.renderer.width !== width || state.renderer.height !== height) { - // Set dpr, handle resize - state.renderer.setSize(width, height) - - // Update projection - const projection = orthographic ? 'orthographic' : 'perspective' - state.camera[projection]({ aspect: width / height }) - } + ) } useIsomorphicLayoutEffect(() => { diff --git a/src/renderer.tsx b/src/renderer.tsx index 75e2f07..9e1626c 100644 --- a/src/renderer.tsx +++ b/src/renderer.tsx @@ -17,7 +17,11 @@ const roots = new Map() export const render = ( element: React.ReactNode, target: HTMLCanvasElement, - { mode = 'blocking', ...config }: RenderProps = {}, + { + mode = 'blocking', + size = { width: target.parentElement?.clientWidth ?? 0, height: target.parentElement?.clientHeight ?? 0 }, + ...config + }: RenderProps = {}, ) => { // Check for existing root, create on first run let root = roots.get(target) @@ -83,6 +87,7 @@ export const render = ( // Create root store const store = create((set: SetState, get: SetState) => ({ + size, renderer, gl, camera, @@ -106,6 +111,38 @@ export const render = ( // Handle callback config.onCreated?.(state) + // Animate + const animate = (time?: number) => { + // Cancel animation if frameloop is set, otherwise keep looping + if (state.frameloop === 'never') return cancelAnimationFrame(state.animation) + state.animation = requestAnimationFrame(animate) + + // Call subscribed elements + for (const ref of state.subscribed) ref.current?.(state, time) + + // If rendering manually, skip render + if (state.priority) return + + // Render to screen + state.renderer.render({ scene: state.scene, camera: state.camera }) + } + if (state.frameloop !== 'never') animate() + + // Handle resize + const onResize = (state: RootState) => { + const { width, height } = state.size + if (state.renderer.width !== width || state.renderer.height !== height) { + // Set dpr, handle resize + state.renderer.setSize(width, height) + + // Update projection + const projection = state.orthographic ? 'orthographic' : 'perspective' + state.camera[projection]({ aspect: width / height }) + } + } + store.subscribe(onResize) + onResize(state) + // Create root fiber const fiber = reconciler.createContainer(state, RENDER_MODES[mode] ?? RENDER_MODES['blocking'], false, null) diff --git a/src/types.ts b/src/types.ts index 3f87af0..1c047fb 100644 --- a/src/types.ts +++ b/src/types.ts @@ -122,6 +122,13 @@ export interface EventManager { disconnect?: (target: HTMLCanvasElement, state: RootState) => void } +export interface Size { + width: number + height: number +} + +export type Frameloop = 'always' | 'never' + /** * useFrame subscription. */ @@ -133,6 +140,9 @@ export type Subscription = (state: RootState, time: number) => any export interface RootState { set: SetState get: SetState + size: Size + orthographic: boolean + frameloop: Frameloop renderer: OGL.Renderer gl: OGL.OGLRenderingContext scene: Omit & { children: any[] } @@ -163,6 +173,9 @@ export type DPR = [number, number] | number * Canvas & imperative render method props. */ export type RenderProps = { + size?: Size + orthographic?: boolean + frameloop?: Frameloop renderer?: | ((canvas: HTMLCanvasElement) => OGL.Renderer) | OGL.Renderer