WIP: refactor sequencing non-scalars - 2
This commit is contained in:
parent
4ec6dd1181
commit
3f91f0bdae
6 changed files with 231 additions and 20 deletions
|
@ -90,6 +90,7 @@ const BasicKeyframedTrack: React.FC<{
|
||||||
layoutP={layoutP}
|
layoutP={layoutP}
|
||||||
sheetObject={sheetObject}
|
sheetObject={sheetObject}
|
||||||
trackId={trackId}
|
trackId={trackId}
|
||||||
|
isScalar={propConfig.isScalar === true}
|
||||||
key={'keyframe-' + kf.id}
|
key={'keyframe-' + kf.id}
|
||||||
extremumSpace={cachedExtremumSpace.current}
|
extremumSpace={cachedExtremumSpace.current}
|
||||||
color={color}
|
color={color}
|
||||||
|
|
|
@ -25,8 +25,8 @@ const Curve: React.FC<IProps> = (props) => {
|
||||||
|
|
||||||
const [contextMenu] = useConnectorContextMenu(node, props)
|
const [contextMenu] = useConnectorContextMenu(node, props)
|
||||||
|
|
||||||
const curValue = typeof cur.value === 'number' ? cur.value : 0
|
const curValue = props.isScalar ? (cur.value as number) : 0
|
||||||
const nextValue = typeof next.value === 'number' ? next.value : 1
|
const nextValue = props.isScalar ? (next.value as number) : 1
|
||||||
const leftYInExtremumSpace = props.extremumSpace.fromValueSpace(curValue)
|
const leftYInExtremumSpace = props.extremumSpace.fromValueSpace(curValue)
|
||||||
const rightYInExtremumSpace = props.extremumSpace.fromValueSpace(nextValue)
|
const rightYInExtremumSpace = props.extremumSpace.fromValueSpace(nextValue)
|
||||||
|
|
||||||
|
|
|
@ -68,12 +68,8 @@ const CurveHandle: React.FC<IProps> = (props) => {
|
||||||
const valInDiffSpace =
|
const valInDiffSpace =
|
||||||
props.which === 'left' ? cur.handles[3] : next.handles[1]
|
props.which === 'left' ? cur.handles[3] : next.handles[1]
|
||||||
|
|
||||||
if (!valInDiffSpace) {
|
const curValue = props.isScalar ? (cur.value as number) : 0
|
||||||
// debugger
|
const nextValue = props.isScalar ? (next.value as number) : 1
|
||||||
}
|
|
||||||
|
|
||||||
const curValue = typeof cur.value === 'number' ? cur.value : 0
|
|
||||||
const nextValue = typeof next.value === 'number' ? next.value : 1
|
|
||||||
|
|
||||||
const value = curValue + (nextValue - curValue) * valInDiffSpace
|
const value = curValue + (nextValue - curValue) * valInDiffSpace
|
||||||
|
|
||||||
|
@ -168,8 +164,8 @@ function useOurDrags(node: SVGCircleElement | null, props: IProps): void {
|
||||||
const dYInValueSpace =
|
const dYInValueSpace =
|
||||||
propsAtStartOfDrag.extremumSpace.deltaToValueSpace(dYInExtremumSpace)
|
propsAtStartOfDrag.extremumSpace.deltaToValueSpace(dYInExtremumSpace)
|
||||||
|
|
||||||
const curValue = typeof cur.value === 'number' ? cur.value : 0
|
const curValue = props.isScalar ? (cur.value as number) : 0
|
||||||
const nextValue = typeof next.value === 'number' ? next.value : 1
|
const nextValue = props.isScalar ? (next.value as number) : 1
|
||||||
const dyInKeyframeDiffSpace = dYInValueSpace / (nextValue - curValue)
|
const dyInKeyframeDiffSpace = dYInValueSpace / (nextValue - curValue)
|
||||||
|
|
||||||
if (propsAtStartOfDrag.which === 'left') {
|
if (propsAtStartOfDrag.which === 'left') {
|
||||||
|
|
|
@ -0,0 +1,199 @@
|
||||||
|
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||||
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
|
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
|
||||||
|
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
|
||||||
|
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
||||||
|
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
||||||
|
import type {VoidFn} from '@theatre/shared/utils/types'
|
||||||
|
import {val} from '@theatre/dataverse'
|
||||||
|
import React, {useMemo, useRef, useState} from 'react'
|
||||||
|
import styled from 'styled-components'
|
||||||
|
import type KeyframeEditor from './KeyframeEditor'
|
||||||
|
import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
|
||||||
|
import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||||
|
import {attributeNameThatLocksFramestamp} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||||
|
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||||
|
import {useCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler'
|
||||||
|
|
||||||
|
export const dotSize = 6
|
||||||
|
|
||||||
|
const Circle = styled.circle`
|
||||||
|
fill: var(--main-color);
|
||||||
|
stroke-width: 1px;
|
||||||
|
vector-effect: non-scaling-stroke;
|
||||||
|
|
||||||
|
r: 2px;
|
||||||
|
`
|
||||||
|
|
||||||
|
const HitZone = styled.circle`
|
||||||
|
stroke-width: 6px;
|
||||||
|
vector-effect: non-scaling-stroke;
|
||||||
|
r: 6px;
|
||||||
|
fill: transparent;
|
||||||
|
${pointerEventsAutoInNormalMode};
|
||||||
|
|
||||||
|
&:hover + ${Circle} {
|
||||||
|
r: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pointer-root.normal & {
|
||||||
|
cursor: ew-resize;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pointer-root.draggingPositionInSequenceEditor & {
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.beingDragged {
|
||||||
|
pointer-events: none !important;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
type IProps = Parameters<typeof KeyframeEditor>[0] & {which: 'left' | 'right'}
|
||||||
|
|
||||||
|
const GraphEditorDotNonScalar: React.FC<IProps> = (props) => {
|
||||||
|
const [ref, node] = useRefAndState<SVGCircleElement | null>(null)
|
||||||
|
|
||||||
|
const {index, trackData} = props
|
||||||
|
const cur = trackData.keyframes[index]
|
||||||
|
const next = trackData.keyframes[index + 1]
|
||||||
|
|
||||||
|
const [contextMenu] = useKeyframeContextMenu(node, props)
|
||||||
|
|
||||||
|
const curValue = props.which === 'left' ? 0 : 1
|
||||||
|
|
||||||
|
const isDragging = useDragKeyframe(node, props)
|
||||||
|
|
||||||
|
const cyInExtremumSpace = props.extremumSpace.fromValueSpace(curValue)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<HitZone
|
||||||
|
ref={ref}
|
||||||
|
style={{
|
||||||
|
// @ts-ignore
|
||||||
|
cx: `calc(var(--unitSpaceToScaledSpaceMultiplier) * ${cur.position} * 1px)`,
|
||||||
|
cy: `calc((var(--graphEditorVerticalSpace) - var(--graphEditorVerticalSpace) * ${cyInExtremumSpace}) * 1px)`,
|
||||||
|
}}
|
||||||
|
{...{
|
||||||
|
[attributeNameThatLocksFramestamp]: cur.position.toFixed(3),
|
||||||
|
}}
|
||||||
|
data-pos={cur.position.toFixed(3)}
|
||||||
|
className={isDragging ? 'beingDragged' : ''}
|
||||||
|
/>
|
||||||
|
<Circle
|
||||||
|
style={{
|
||||||
|
// @ts-ignore
|
||||||
|
cx: `calc(var(--unitSpaceToScaledSpaceMultiplier) * ${cur.position} * 1px)`,
|
||||||
|
cy: `calc((var(--graphEditorVerticalSpace) - var(--graphEditorVerticalSpace) * ${cyInExtremumSpace}) * 1px)`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{contextMenu}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GraphEditorDotNonScalar
|
||||||
|
|
||||||
|
function useDragKeyframe(
|
||||||
|
node: SVGCircleElement | null,
|
||||||
|
_props: IProps,
|
||||||
|
): boolean {
|
||||||
|
const [isDragging, setIsDragging] = useState(false)
|
||||||
|
useLockFrameStampPosition(isDragging, _props.keyframe.position)
|
||||||
|
const propsRef = useRef(_props)
|
||||||
|
propsRef.current = _props
|
||||||
|
|
||||||
|
const gestureHandlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
|
||||||
|
let toUnitSpace: SequenceEditorPanelLayout['scaledSpace']['toUnitSpace']
|
||||||
|
|
||||||
|
let propsAtStartOfDrag: IProps
|
||||||
|
let tempTransaction: CommitOrDiscard | undefined
|
||||||
|
let unlockExtremums: VoidFn | undefined
|
||||||
|
|
||||||
|
return {
|
||||||
|
lockCursorTo: 'ew-resize',
|
||||||
|
onDragStart(event) {
|
||||||
|
setIsDragging(true)
|
||||||
|
|
||||||
|
propsAtStartOfDrag = propsRef.current
|
||||||
|
|
||||||
|
toUnitSpace = val(propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace)
|
||||||
|
|
||||||
|
unlockExtremums = propsAtStartOfDrag.extremumSpace.lock()
|
||||||
|
},
|
||||||
|
onDrag(dx, dy) {
|
||||||
|
const original =
|
||||||
|
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index]
|
||||||
|
|
||||||
|
const deltaPos = toUnitSpace(dx)
|
||||||
|
|
||||||
|
const updatedKeyframes: Keyframe[] = []
|
||||||
|
|
||||||
|
const cur: Keyframe = {
|
||||||
|
...original,
|
||||||
|
position: original.position + deltaPos,
|
||||||
|
value: original.value,
|
||||||
|
handles: [...original.handles],
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedKeyframes.push(cur)
|
||||||
|
|
||||||
|
tempTransaction?.discard()
|
||||||
|
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
||||||
|
stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes(
|
||||||
|
{
|
||||||
|
...propsAtStartOfDrag.sheetObject.address,
|
||||||
|
trackId: propsAtStartOfDrag.trackId,
|
||||||
|
keyframes: updatedKeyframes,
|
||||||
|
snappingFunction: val(
|
||||||
|
propsAtStartOfDrag.layoutP.sheet,
|
||||||
|
).getSequence().closestGridPosition,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onDragEnd(dragHappened) {
|
||||||
|
setIsDragging(false)
|
||||||
|
if (unlockExtremums) {
|
||||||
|
const unlock = unlockExtremums
|
||||||
|
unlockExtremums = undefined
|
||||||
|
unlock()
|
||||||
|
}
|
||||||
|
if (dragHappened) {
|
||||||
|
tempTransaction?.commit()
|
||||||
|
} else {
|
||||||
|
tempTransaction?.discard()
|
||||||
|
}
|
||||||
|
tempTransaction = undefined
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useDrag(node, gestureHandlers)
|
||||||
|
useCursorLock(isDragging, 'draggingPositionInSequenceEditor', 'ew-resize')
|
||||||
|
return isDragging
|
||||||
|
}
|
||||||
|
|
||||||
|
function useKeyframeContextMenu(node: SVGCircleElement | null, props: IProps) {
|
||||||
|
return useContextMenu(node, {
|
||||||
|
items: () => {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: 'Delete',
|
||||||
|
callback: () => {
|
||||||
|
getStudio()!.transaction(({stateEditors}) => {
|
||||||
|
stateEditors.coreByProject.historic.sheetsById.sequence.deleteKeyframes(
|
||||||
|
{
|
||||||
|
...props.sheetObject.address,
|
||||||
|
keyframeIds: [props.keyframe.id],
|
||||||
|
trackId: props.trackId,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
|
@ -51,7 +51,7 @@ const HitZone = styled.circle`
|
||||||
|
|
||||||
type IProps = Parameters<typeof KeyframeEditor>[0]
|
type IProps = Parameters<typeof KeyframeEditor>[0]
|
||||||
|
|
||||||
const Dot: React.FC<IProps> = (props) => {
|
const GraphEditorDotScalar: React.FC<IProps> = (props) => {
|
||||||
const [ref, node] = useRefAndState<SVGCircleElement | null>(null)
|
const [ref, node] = useRefAndState<SVGCircleElement | null>(null)
|
||||||
|
|
||||||
const {index, trackData} = props
|
const {index, trackData} = props
|
||||||
|
@ -59,12 +59,12 @@ const Dot: React.FC<IProps> = (props) => {
|
||||||
const next = trackData.keyframes[index + 1]
|
const next = trackData.keyframes[index + 1]
|
||||||
|
|
||||||
const [contextMenu] = useKeyframeContextMenu(node, props)
|
const [contextMenu] = useKeyframeContextMenu(node, props)
|
||||||
const isDragging =
|
|
||||||
typeof cur.value === 'number' ? useDragKeyframe(node, props) : false
|
|
||||||
|
|
||||||
const cyInExtremumSpace = props.extremumSpace.fromValueSpace(
|
const curValue = cur.value as number
|
||||||
typeof cur.value === 'number' ? cur.value : 0,
|
|
||||||
)
|
const isDragging = useDragKeyframe(node, props)
|
||||||
|
|
||||||
|
const cyInExtremumSpace = props.extremumSpace.fromValueSpace(curValue)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -93,7 +93,7 @@ const Dot: React.FC<IProps> = (props) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Dot
|
export default GraphEditorDotScalar
|
||||||
|
|
||||||
function useDragKeyframe(
|
function useDragKeyframe(
|
||||||
node: SVGCircleElement | null,
|
node: SVGCircleElement | null,
|
||||||
|
@ -146,6 +146,7 @@ function useDragKeyframe(
|
||||||
value: (original.value as number) + dYInValueSpace,
|
value: (original.value as number) + dYInValueSpace,
|
||||||
handles: [...original.handles],
|
handles: [...original.handles],
|
||||||
}
|
}
|
||||||
|
|
||||||
updatedKeyframes.push(cur)
|
updatedKeyframes.push(cur)
|
||||||
|
|
||||||
if (keepSpeeds) {
|
if (keepSpeeds) {
|
|
@ -12,7 +12,8 @@ import type {graphEditorColors} from '@theatre/studio/panels/SequenceEditorPanel
|
||||||
import type {ExtremumSpace} from '@theatre/studio/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/BasicKeyframedTrack'
|
import type {ExtremumSpace} from '@theatre/studio/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/BasicKeyframedTrack'
|
||||||
import Curve from './Curve'
|
import Curve from './Curve'
|
||||||
import CurveHandle from './CurveHandle'
|
import CurveHandle from './CurveHandle'
|
||||||
import Dot from './Dot'
|
import GraphEditorDotScalar from './GraphEditorDotScalar'
|
||||||
|
import GraphEditorDotNonScalar from './GraphEditorDotNonScalar'
|
||||||
|
|
||||||
const Container = styled.g`
|
const Container = styled.g`
|
||||||
/* position: absolute; */
|
/* position: absolute; */
|
||||||
|
@ -28,9 +29,10 @@ const KeyframeEditor: React.FC<{
|
||||||
trackId: SequenceTrackId
|
trackId: SequenceTrackId
|
||||||
sheetObject: SheetObject
|
sheetObject: SheetObject
|
||||||
extremumSpace: ExtremumSpace
|
extremumSpace: ExtremumSpace
|
||||||
|
isScalar: boolean
|
||||||
color: keyof typeof graphEditorColors
|
color: keyof typeof graphEditorColors
|
||||||
}> = (props) => {
|
}> = (props) => {
|
||||||
const {index, trackData} = props
|
const {index, trackData, isScalar} = props
|
||||||
const cur = trackData.keyframes[index]
|
const cur = trackData.keyframes[index]
|
||||||
const next = trackData.keyframes[index + 1]
|
const next = trackData.keyframes[index + 1]
|
||||||
|
|
||||||
|
@ -48,7 +50,19 @@ const KeyframeEditor: React.FC<{
|
||||||
) : (
|
) : (
|
||||||
noConnector
|
noConnector
|
||||||
)}
|
)}
|
||||||
<Dot {...props} />
|
{isScalar ? (
|
||||||
|
<GraphEditorDotScalar {...props} />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<GraphEditorDotNonScalar {...props} which="left" />
|
||||||
|
{shouldShowCurve && (
|
||||||
|
<GraphEditorDotNonScalar
|
||||||
|
{...{...props, index: index + 1, keyframe: next}}
|
||||||
|
which="right"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue