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', 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')}> <SheetProvider sheet={getProject('Space').sheet('Scene')}>
<fog attach="fog" args={[bg, 16, 30]} /> <fog attach="fog" args={[bg, 16, 30]} />
<color attach="background" args={[bg]} /> <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 studio, {ToolbarIconButton} from '@theatre/studio'
import {useVal} from '@theatre/react' import {useVal} from '@theatre/react'
import styled, {createGlobalStyle, StyleSheetManager} from 'styled-components' import styled, {createGlobalStyle, StyleSheetManager} from 'styled-components'
import {IoCameraReverseOutline} from 'react-icons/io5'
import type {ISheet} from '@theatre/core' import type {ISheet} from '@theatre/core'
import useSnapshotEditorCamera from './useSnapshotEditorCamera' import useSnapshotEditorCamera from './useSnapshotEditorCamera'
import {getEditorSheet, getEditorSheetObject} from './editorStuff' import {getEditorSheet, getEditorSheetObject} from './editorStuff'
import type {$IntentionalAny} from '@theatre/shared/utils/types' import type {$IntentionalAny} from '@theatre/shared/utils/types'
import {InfiniteGridHelper} from '../InfiniteGridHelper' import {InfiniteGridHelper} from '../InfiniteGridHelper'
import {DragDetectorProvider} from './DragDetector' import {DragDetectorProvider} from './DragDetector'
import TooltipPortalProvider from './TooltipPortalProvider'
import {FiRefreshCw} from 'react-icons/fi'
import ReferenceWindow from './ReferenceWindow/ReferenceWindow'
const GlobalStyle = createGlobalStyle` const GlobalStyle = createGlobalStyle`
:host { :host {
@ -97,16 +99,26 @@ const Overlay = styled.div`
const Tools = styled.div` const Tools = styled.div`
position: absolute; position: absolute;
left: 8px; left: 12px;
top: 6px; top: 12px;
pointer-events: auto; pointer-events: auto;
` `
const ReferenceWindowContainer = styled.div`
position: absolute;
right: 12px;
top: 12px;
justify-content: center;
`
const SnapshotEditor: React.FC<{paneId: string}> = (props) => { const SnapshotEditor: React.FC<{paneId: string}> = (props) => {
const snapshotEditorSheet = getEditorSheet() const snapshotEditorSheet = getEditorSheet()
const paneId = props.paneId const paneId = props.paneId
const editorObject = getEditorSheetObject() const editorObject = getEditorSheetObject()
const showReferenceWindow =
useVal(editorObject?.props.viewport.showReferenceWindow) ?? true
const [sceneSnapshot, createSnapshot] = useEditorStore( const [sceneSnapshot, createSnapshot] = useEditorStore(
(state) => [state.sceneSnapshot, state.createSnapshot], (state) => [state.sceneSnapshot, state.createSnapshot],
shallow, shallow,
@ -148,6 +160,7 @@ const SnapshotEditor: React.FC<{paneId: string}> = (props) => {
<StyleSheetManager disableVendorPrefixes> <StyleSheetManager disableVendorPrefixes>
<> <>
<GlobalStyle /> <GlobalStyle />
<TooltipPortalProvider>
<Wrapper> <Wrapper>
<Overlay> <Overlay>
<Tools> <Tools>
@ -155,9 +168,14 @@ const SnapshotEditor: React.FC<{paneId: string}> = (props) => {
title="Refresh Snapshot" title="Refresh Snapshot"
onClick={createSnapshot} onClick={createSnapshot}
> >
<IoCameraReverseOutline /> <FiRefreshCw />
</ToolbarIconButton> </ToolbarIconButton>
</Tools> </Tools>
{showReferenceWindow && (
<ReferenceWindowContainer>
<ReferenceWindow height={120} />
</ReferenceWindowContainer>
)}
</Overlay> </Overlay>
{sceneSnapshot ? ( {sceneSnapshot ? (
@ -184,7 +202,7 @@ const SnapshotEditor: React.FC<{paneId: string}> = (props) => {
</> </>
) : null} ) : null}
</Wrapper> </Wrapper>
{/* </PortalContext.Provider> */} </TooltipPortalProvider>
</> </>
</StyleSheetManager> </StyleSheetManager>
</root.div> </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'}, {as: 'menu', label: 'Shading'},
), ),
showReferenceWindow: types.boolean(true, {
label: 'Reference',
}),
}, },
{label: 'Viewport Config'}, {label: 'Viewport Config'},
), ),