152 lines
4 KiB
TypeScript
152 lines
4 KiB
TypeScript
import type {MutableRefObject} from 'react'
|
|
import React, {
|
|
useCallback,
|
|
useEffect,
|
|
useLayoutEffect,
|
|
useMemo,
|
|
useRef,
|
|
useState,
|
|
} from 'react'
|
|
import studio from '@theatre/studio'
|
|
import type {ISheet} from '@theatre/core'
|
|
import {types} from '@theatre/core'
|
|
import type {ITurtle} from './turtle'
|
|
import {drawTurtlePlan, makeTurtlePlan} from './turtle'
|
|
|
|
studio.initialize()
|
|
|
|
const objConfig = {
|
|
startingPoint: {
|
|
x: types.number(0.5, {range: [0, 1]}),
|
|
y: types.number(0.5, {range: [0, 1]}),
|
|
},
|
|
scale: types.number(1, {range: [0.1, 1000]}),
|
|
}
|
|
|
|
const TurtleRenderer: React.FC<{
|
|
sheet: ISheet
|
|
objKey: string
|
|
width: number
|
|
height: number
|
|
programFn: (t: ITurtle) => void
|
|
}> = (props) => {
|
|
const [canvas, setCanvas] = useState<HTMLCanvasElement | null>(null)
|
|
|
|
const context = useMemo(() => {
|
|
if (canvas) {
|
|
return canvas.getContext('2d')!
|
|
}
|
|
}, [canvas])
|
|
|
|
const dimsRef = useRef({width: props.width, height: props.height})
|
|
dimsRef.current = {width: props.width, height: props.height}
|
|
|
|
const obj = useMemo(() => {
|
|
return props.sheet.object(props.objKey, objConfig)
|
|
}, [props.sheet, props.objKey])
|
|
|
|
useEffect(() => {
|
|
obj.onValuesChange((v) => {
|
|
setTransforms(v)
|
|
})
|
|
}, [obj])
|
|
|
|
const [transforms, transformsRef, setTransforms] = useStateAndRef<
|
|
typeof obj.value
|
|
>({scale: 1, startingPoint: {x: 0.5, y: 0.5}})
|
|
|
|
const bounds = useMemo(() => canvas?.getBoundingClientRect(), [canvas])
|
|
|
|
useLayoutEffect(() => {
|
|
if (!canvas) return
|
|
|
|
const receiveWheelEvent = (event: WheelEvent) => {
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
const oldTransform = transformsRef.current
|
|
const newTransform: typeof oldTransform = {
|
|
...oldTransform,
|
|
startingPoint: {...oldTransform.startingPoint},
|
|
}
|
|
|
|
if (event.ctrlKey) {
|
|
const scaleFactor = 1 - (event.deltaY / dimsRef.current.height) * 1.2
|
|
newTransform.scale *= scaleFactor
|
|
|
|
// const bounds = canvas.getBoundingClientRect()
|
|
|
|
const anchorPoint = {
|
|
x: (event.clientX - bounds!.left) / dimsRef.current.width,
|
|
y: (event.clientY - bounds!.top) / dimsRef.current.height,
|
|
}
|
|
|
|
newTransform.startingPoint.x =
|
|
anchorPoint.x -
|
|
(anchorPoint.x - newTransform.startingPoint.x) * scaleFactor
|
|
|
|
newTransform.startingPoint.y =
|
|
anchorPoint.y -
|
|
(anchorPoint.y - newTransform.startingPoint.y) * scaleFactor
|
|
} else {
|
|
newTransform.startingPoint.x =
|
|
oldTransform.startingPoint.x - event.deltaX / dimsRef.current.width
|
|
newTransform.startingPoint.y =
|
|
oldTransform.startingPoint.y - event.deltaY / dimsRef.current.height
|
|
}
|
|
studio.transaction((api) => {
|
|
api.set(obj.props, newTransform)
|
|
})
|
|
// setTransforms(newTransform)
|
|
}
|
|
|
|
const listenerOptions = {
|
|
capture: true,
|
|
passive: false,
|
|
}
|
|
canvas.addEventListener('wheel', receiveWheelEvent, listenerOptions)
|
|
|
|
return () => {
|
|
canvas.removeEventListener('wheel', receiveWheelEvent, listenerOptions)
|
|
}
|
|
}, [canvas])
|
|
|
|
const plan = useMemo(() => makeTurtlePlan(props.programFn), [props.programFn])
|
|
|
|
useEffect(() => {
|
|
if (!context) return
|
|
|
|
drawTurtlePlan(
|
|
plan,
|
|
context,
|
|
{
|
|
width: props.width,
|
|
height: props.height,
|
|
scale: transforms.scale,
|
|
startFrom: {
|
|
x: transforms.startingPoint.x * props.width,
|
|
y: transforms.startingPoint.y * props.height,
|
|
},
|
|
},
|
|
1,
|
|
)
|
|
}, [props.width, props.height, plan, context, transforms])
|
|
|
|
return (
|
|
<canvas width={props.width} height={props.height} ref={setCanvas}></canvas>
|
|
)
|
|
}
|
|
|
|
function useStateAndRef<S>(
|
|
initial: S,
|
|
): [S, MutableRefObject<S>, (s: S) => void] {
|
|
const [state, setState] = useState(initial)
|
|
const stateRef = useRef(state)
|
|
const set = useCallback((s: S) => {
|
|
stateRef.current = s
|
|
setState(s)
|
|
}, [])
|
|
|
|
return [state, stateRef, set]
|
|
}
|
|
|
|
export default TurtleRenderer
|