Create reference window for r3f (#169)

* Add reference window (and fix tooltips)

* Replace image with data url so the build doesn't fail
This commit is contained in:
Andrew Prifer 2022-05-18 17:04:07 +02:00 committed by GitHub
parent 8520c74116
commit 95d4e1d315
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 247 additions and 40 deletions

View file

@ -53,7 +53,13 @@ function App() {
height: '100vh',
}}
>
<Canvas dpr={[1.5, 2]} linear shadows frameloop="demand">
<Canvas
dpr={[1.5, 2]}
linear
shadows
gl={{preserveDrawingBuffer: true}}
frameloop="demand"
>
<SheetProvider sheet={getProject('Space').sheet('Scene')}>
<fog attach="fog" args={[bg, 16, 30]} />
<color attach="background" args={[bg]} />

View file

@ -0,0 +1,149 @@
import type {VFC} from 'react'
import React, {useEffect, useLayoutEffect, useRef} from 'react'
import {useEditorStore} from '../../store'
import shallow from 'zustand/shallow'
import type {WebGLRenderer} from 'three'
import useMeasure from 'react-use-measure'
import styled, {keyframes} from 'styled-components'
import {TiWarningOutline} from 'react-icons/ti'
// This is ugly, but pure TS doesn't let you do bundler-stuff
import noiseImageUrl from './noiseImage'
const Container = styled.div`
position: relative;
width: fit-content;
height: fit-content;
border-radius: 4px;
overflow: hidden;
`
const Canvas = styled.canvas`
display: block;
`
const staticAnimation = keyframes`
0% { transform: translate(0,0) }
10% { transform: translate(-5%,-5%) }
20% { transform: translate(-10%,5%) }
30% { transform: translate(5%,-10%) }
40% { transform: translate(-5%,15%) }
50% { transform: translate(-10%,5%) }
60% { transform: translate(15%,0) }
70% { transform: translate(0,10%) }
80% { transform: translate(-15%,0) }
90% { transform: translate(10%,5%) }
100% { transform: translate(5%,0) }
`
const Static = styled.div`
position: relative;
display: flex;
width: 200px;
height: 120px;
padding: 18px;
::before {
content: '';
position: absolute;
z-index: -1;
top: -50%;
left: -50%;
right: -50%;
bottom: -50%;
background: #2f2f2f url(${noiseImageUrl}) repeat 0 0;
animation: ${staticAnimation} 0.2s infinite;
}
`
const Warning = styled.div`
display: flex;
flex-direction: column;
gap: 12px;
align-items: center;
text-align: center;
opacity: 0.8;
`
interface ReferenceWindowProps {
height: number
}
const ReferenceWindow: VFC<ReferenceWindowProps> = ({height}) => {
const canvasRef = useRef<HTMLCanvasElement>(null)
const [gl] = useEditorStore((state) => [state.gl], shallow)
const [ref, bounds] = useMeasure()
const preserveDrawingBuffer =
(
gl?.domElement.getContext('webgl') ?? gl?.domElement.getContext('webgl2')
)?.getContextAttributes()?.preserveDrawingBuffer ?? false
useLayoutEffect(() => {
if (gl) {
ref(gl?.domElement)
}
}, [gl, ref])
useEffect(() => {
let animationHandle: number
const draw = (gl: WebGLRenderer) => () => {
animationHandle = requestAnimationFrame(draw(gl))
if (!gl.domElement || !preserveDrawingBuffer) {
cancelAnimationFrame(animationHandle)
return
}
const width = (gl.domElement.width / gl.domElement.height) * height
const ctx = canvasRef.current!.getContext('2d')!
console.log(gl.domElement.getContext('webgl2')!.getContextAttributes())
// https://stackoverflow.com/questions/17861447/html5-canvas-drawimage-how-to-apply-antialiasing
ctx.imageSmoothingQuality = 'high'
ctx.fillStyle = 'white'
ctx.fillRect(0, 0, width, height)
ctx.drawImage(gl.domElement, 0, 0, width, height)
}
if (gl) {
draw(gl)()
}
return () => {
cancelAnimationFrame(animationHandle)
}
}, [gl, height, preserveDrawingBuffer])
return (
<Container>
{gl?.domElement && preserveDrawingBuffer ? (
<Canvas
ref={canvasRef}
width={
((bounds.width || gl.domElement.width) /
(bounds.height || gl.domElement.height)) *
height
}
height={height}
/>
) : (
<Static>
<Warning>
<TiWarningOutline size="3em" />
<code>
Please set <pre>{`gl={{ preserveDrawingBuffer: true }}`}</pre> on
the r3f Canvas for the reference window to work.
</code>
</Warning>
</Static>
)}
</Container>
)
}
export default ReferenceWindow

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

View file

@ -9,13 +9,15 @@ import ProxyManager from './ProxyManager'
import studio, {ToolbarIconButton} from '@theatre/studio'
import {useVal} from '@theatre/react'
import styled, {createGlobalStyle, StyleSheetManager} from 'styled-components'
import {IoCameraReverseOutline} from 'react-icons/io5'
import type {ISheet} from '@theatre/core'
import useSnapshotEditorCamera from './useSnapshotEditorCamera'
import {getEditorSheet, getEditorSheetObject} from './editorStuff'
import type {$IntentionalAny} from '@theatre/shared/utils/types'
import {InfiniteGridHelper} from '../InfiniteGridHelper'
import {DragDetectorProvider} from './DragDetector'
import TooltipPortalProvider from './TooltipPortalProvider'
import {FiRefreshCw} from 'react-icons/fi'
import ReferenceWindow from './ReferenceWindow/ReferenceWindow'
const GlobalStyle = createGlobalStyle`
:host {
@ -97,16 +99,26 @@ const Overlay = styled.div`
const Tools = styled.div`
position: absolute;
left: 8px;
top: 6px;
left: 12px;
top: 12px;
pointer-events: auto;
`
const ReferenceWindowContainer = styled.div`
position: absolute;
right: 12px;
top: 12px;
justify-content: center;
`
const SnapshotEditor: React.FC<{paneId: string}> = (props) => {
const snapshotEditorSheet = getEditorSheet()
const paneId = props.paneId
const editorObject = getEditorSheetObject()
const showReferenceWindow =
useVal(editorObject?.props.viewport.showReferenceWindow) ?? true
const [sceneSnapshot, createSnapshot] = useEditorStore(
(state) => [state.sceneSnapshot, state.createSnapshot],
shallow,
@ -148,43 +160,49 @@ const SnapshotEditor: React.FC<{paneId: string}> = (props) => {
<StyleSheetManager disableVendorPrefixes>
<>
<GlobalStyle />
<Wrapper>
<Overlay>
<Tools>
<ToolbarIconButton
title="Refresh Snapshot"
onClick={createSnapshot}
>
<IoCameraReverseOutline />
</ToolbarIconButton>
</Tools>
</Overlay>
{sceneSnapshot ? (
<>
<CanvasWrapper>
<Canvas
// @ts-ignore
colorManagement
onCreated={({gl}) => {
gl.setClearColor('white')
}}
shadowMap
dpr={[1, 2]}
fog={'red'}
frameloop="demand"
onPointerMissed={onPointerMissed}
<TooltipPortalProvider>
<Wrapper>
<Overlay>
<Tools>
<ToolbarIconButton
title="Refresh Snapshot"
onClick={createSnapshot}
>
<EditorScene
snapshotEditorSheet={snapshotEditorSheet}
paneId={paneId}
/>
</Canvas>
</CanvasWrapper>
</>
) : null}
</Wrapper>
{/* </PortalContext.Provider> */}
<FiRefreshCw />
</ToolbarIconButton>
</Tools>
{showReferenceWindow && (
<ReferenceWindowContainer>
<ReferenceWindow height={120} />
</ReferenceWindowContainer>
)}
</Overlay>
{sceneSnapshot ? (
<>
<CanvasWrapper>
<Canvas
// @ts-ignore
colorManagement
onCreated={({gl}) => {
gl.setClearColor('white')
}}
shadowMap
dpr={[1, 2]}
fog={'red'}
frameloop="demand"
onPointerMissed={onPointerMissed}
>
<EditorScene
snapshotEditorSheet={snapshotEditorSheet}
paneId={paneId}
/>
</Canvas>
</CanvasWrapper>
</>
) : null}
</Wrapper>
</TooltipPortalProvider>
</>
</StyleSheetManager>
</root.div>

View file

@ -0,0 +1,27 @@
import type {ReactNode} from 'react';
import React, { useState} from 'react'
import {PortalContext} from 'reakit'
import styled from 'styled-components'
const PortalHost = styled.div`
position: fixed;
inset: 0;
pointer-events: none;
`
export interface PortalManagerProps {
children: ReactNode
}
const TooltipPortalProvider: React.VFC<PortalManagerProps> = ({children}) => {
const [wrapper, setWrapper] = useState<HTMLDivElement | null>(null)
return (
<PortalContext.Provider value={wrapper}>
{children}
<PortalHost ref={setWrapper} />
</PortalContext.Provider>
)
}
export default TooltipPortalProvider

View file

@ -22,6 +22,9 @@ const editorSheetObjectConfig = {
},
{as: 'menu', label: 'Shading'},
),
showReferenceWindow: types.boolean(true, {
label: 'Reference',
}),
},
{label: 'Viewport Config'},
),