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:
parent
8520c74116
commit
95d4e1d315
7 changed files with 247 additions and 40 deletions
|
@ -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]} />
|
||||
|
|
149
packages/r3f/src/components/ReferenceWindow/ReferenceWindow.tsx
Normal file
149
packages/r3f/src/components/ReferenceWindow/ReferenceWindow.tsx
Normal 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
|
@ -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,6 +160,7 @@ const SnapshotEditor: React.FC<{paneId: string}> = (props) => {
|
|||
<StyleSheetManager disableVendorPrefixes>
|
||||
<>
|
||||
<GlobalStyle />
|
||||
<TooltipPortalProvider>
|
||||
<Wrapper>
|
||||
<Overlay>
|
||||
<Tools>
|
||||
|
@ -155,9 +168,14 @@ const SnapshotEditor: React.FC<{paneId: string}> = (props) => {
|
|||
title="Refresh Snapshot"
|
||||
onClick={createSnapshot}
|
||||
>
|
||||
<IoCameraReverseOutline />
|
||||
<FiRefreshCw />
|
||||
</ToolbarIconButton>
|
||||
</Tools>
|
||||
{showReferenceWindow && (
|
||||
<ReferenceWindowContainer>
|
||||
<ReferenceWindow height={120} />
|
||||
</ReferenceWindowContainer>
|
||||
)}
|
||||
</Overlay>
|
||||
|
||||
{sceneSnapshot ? (
|
||||
|
@ -184,7 +202,7 @@ const SnapshotEditor: React.FC<{paneId: string}> = (props) => {
|
|||
</>
|
||||
) : null}
|
||||
</Wrapper>
|
||||
{/* </PortalContext.Provider> */}
|
||||
</TooltipPortalProvider>
|
||||
</>
|
||||
</StyleSheetManager>
|
||||
</root.div>
|
||||
|
|
27
packages/r3f/src/components/TooltipPortalProvider.tsx
Normal file
27
packages/r3f/src/components/TooltipPortalProvider.tsx
Normal 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
|
|
@ -22,6 +22,9 @@ const editorSheetObjectConfig = {
|
|||
},
|
||||
{as: 'menu', label: 'Shading'},
|
||||
),
|
||||
showReferenceWindow: types.boolean(true, {
|
||||
label: 'Reference',
|
||||
}),
|
||||
},
|
||||
{label: 'Viewport Config'},
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue