r3f fix: Pinching creates more than one undo level in r3f P-202
See https://linear.app/theatre/issue/P-202/pinching-creates-more-than-one-undo-level-in-r3f-history-bug
This commit is contained in:
parent
56a059fdd2
commit
8bd37a28d6
1 changed files with 59 additions and 10 deletions
|
@ -11,6 +11,7 @@ import type {ISheet} from '@theatre/core'
|
|||
import {types} from '@theatre/core'
|
||||
import type {ISheetObject} from '@theatre/core'
|
||||
import {useThree} from '@react-three/fiber'
|
||||
import type {$IntentionalAny} from '../../types'
|
||||
|
||||
const camConf = {
|
||||
transform: {
|
||||
|
@ -81,6 +82,20 @@ export default function useSnapshotEditorCamera(
|
|||
return [node, orbitControlsRef]
|
||||
}
|
||||
|
||||
/**
|
||||
* This debounce does delay the placement of a keyframe, so you
|
||||
* need to be wary of it being too long and causing an issue like
|
||||
* the following:
|
||||
*
|
||||
* 1. user moves playhead t 0
|
||||
* 2. user changes orbit
|
||||
* 3. user moves playead to 10
|
||||
* 4. user changes orbit again
|
||||
*
|
||||
* User expects a keyframe on t0 and t10
|
||||
*/
|
||||
const COMMIT_DEBOUNCE_MS = 200
|
||||
|
||||
function usePassValuesFromOrbitControlsToTheatre(
|
||||
cam: PerspectiveCameraImpl | undefined,
|
||||
orbitControls: OrbitControlsImpl | null,
|
||||
|
@ -89,23 +104,52 @@ function usePassValuesFromOrbitControlsToTheatre(
|
|||
useLayoutEffect(() => {
|
||||
if (!cam || orbitControls == null) return
|
||||
|
||||
let currentScrub: undefined | IScrub
|
||||
let currentScrub:
|
||||
| undefined
|
||||
| {
|
||||
/** "debounce" like timer for making commits to the orbit's changes */
|
||||
scheduledCommit?: {
|
||||
timer: $IntentionalAny
|
||||
// Future, might be possible to remember the position we need to apply this scrub to (when the last 'end' event was received)
|
||||
// position: number
|
||||
}
|
||||
scrub: IScrub
|
||||
}
|
||||
|
||||
let started = false
|
||||
const scheduleCommit = () => {
|
||||
if (currentScrub) {
|
||||
clearTimeout(currentScrub.scheduledCommit?.timer)
|
||||
const reference = currentScrub // capture current scrub to make sure currentScrub isn't out of date
|
||||
currentScrub.scheduledCommit = {
|
||||
timer: setTimeout(() => {
|
||||
if (reference === currentScrub) {
|
||||
currentScrub.scrub.commit()
|
||||
currentScrub = undefined
|
||||
}
|
||||
}, COMMIT_DEBOUNCE_MS),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onStart = () => {
|
||||
started = true
|
||||
currentScrub = studio.scrub()
|
||||
}
|
||||
const onEnd = () => {
|
||||
if (currentScrub) {
|
||||
currentScrub.commit()
|
||||
// prevent existing scheduled commit from executing during the start of a rotation or pan.
|
||||
// This currentScrub will continue being used and will be scheduled again onEnd
|
||||
clearTimeout(currentScrub.scheduledCommit?.timer)
|
||||
} else {
|
||||
// start new scrub
|
||||
currentScrub = {
|
||||
scrub: studio.scrub(),
|
||||
}
|
||||
started = false
|
||||
}
|
||||
}
|
||||
|
||||
const onEnd = () => {
|
||||
scheduleCommit()
|
||||
}
|
||||
|
||||
const onChange = () => {
|
||||
if (!started) return
|
||||
if (!currentScrub) return
|
||||
|
||||
const p = cam!.position
|
||||
const position = {x: p.x, y: p.y, z: p.z}
|
||||
|
@ -118,7 +162,7 @@ function usePassValuesFromOrbitControlsToTheatre(
|
|||
target,
|
||||
}
|
||||
|
||||
currentScrub!.capture(({set}) => {
|
||||
currentScrub.scrub.capture(({set}) => {
|
||||
set(objRef.current!.props.transform, transform)
|
||||
})
|
||||
}
|
||||
|
@ -131,6 +175,11 @@ function usePassValuesFromOrbitControlsToTheatre(
|
|||
orbitControls.removeEventListener('start', onStart)
|
||||
orbitControls.removeEventListener('end', onEnd)
|
||||
orbitControls.removeEventListener('change', onChange)
|
||||
if (currentScrub) {
|
||||
// defensively discard in progress changes
|
||||
currentScrub.scrub.discard()
|
||||
currentScrub = undefined
|
||||
}
|
||||
}
|
||||
}, [cam, orbitControls])
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue