Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add size render prop #38

Merged
merged 1 commit into from
Jun 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 7 additions & 33 deletions src/Canvas.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ import '@expo/browser-polyfill'

export type GLContext = ExpoWebGLRenderingContext | WebGLRenderingContext

export interface CanvasProps extends Omit<RenderProps, 'dpr'>, ViewProps {
export interface CanvasProps extends Omit<RenderProps, 'size' | 'dpr'>, ViewProps {
children: React.ReactNode
style?: ViewStyle
orthographic?: boolean
frameloop?: 'always' | 'never'
}

/**
Expand Down Expand Up @@ -54,15 +52,18 @@ export const Canvas = React.forwardRef<View, CanvasProps>(function Canvas(
setCanvas(canvasShim)
}, [])

// Render to screen
if (canvas && width > 0 && height > 0) {
// Render to screen
const state = render(
render(
<ErrorBoundary set={setError}>
<React.Suspense fallback={<Block set={setBlock} />}>{children}</React.Suspense>
</ErrorBoundary>,
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
Expand All @@ -80,40 +81,13 @@ export const Canvas = React.forwardRef<View, CanvasProps>(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
Expand Down
44 changes: 8 additions & 36 deletions src/Canvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<HTMLDivElement> {
export interface CanvasProps extends Omit<RenderProps, 'size'>, React.HTMLAttributes<HTMLDivElement> {
children: React.ReactNode
resize?: ResizeOptions
orthographic?: boolean
frameloop?: 'always' | 'never'
}

/**
Expand Down Expand Up @@ -50,51 +48,25 @@ export const Canvas = React.forwardRef<HTMLCanvasElement, CanvasProps>(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(
<ErrorBoundary set={setError}>
<React.Suspense fallback={<Block set={setBlock} />}>{children}</React.Suspense>
</ErrorBoundary>,
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(() => {
Expand Down
39 changes: 38 additions & 1 deletion src/renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ const roots = new Map<HTMLCanvasElement, { fiber: Fiber; store: RootStore }>()
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)
Expand Down Expand Up @@ -83,6 +87,7 @@ export const render = (

// Create root store
const store = create((set: SetState<RootState>, get: SetState<RootState>) => ({
size,
renderer,
gl,
camera,
Expand All @@ -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)

Expand Down
13 changes: 13 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -133,6 +140,9 @@ export type Subscription = (state: RootState, time: number) => any
export interface RootState {
set: SetState<RootState>
get: SetState<RootState>
size: Size
orthographic: boolean
frameloop: Frameloop
renderer: OGL.Renderer
gl: OGL.OGLRenderingContext
scene: Omit<OGL.Transform, 'children'> & { children: any[] }
Expand Down Expand Up @@ -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
Expand Down