WIP: refactor sequencing non-scalars - 2

This commit is contained in:
Aria Minaei 2021-11-14 20:38:22 +01:00
parent 4ec6dd1181
commit 3f91f0bdae
6 changed files with 231 additions and 20 deletions

View file

@ -90,6 +90,7 @@ const BasicKeyframedTrack: React.FC<{
layoutP={layoutP}
sheetObject={sheetObject}
trackId={trackId}
isScalar={propConfig.isScalar === true}
key={'keyframe-' + kf.id}
extremumSpace={cachedExtremumSpace.current}
color={color}

View file

@ -25,8 +25,8 @@ const Curve: React.FC<IProps> = (props) => {
const [contextMenu] = useConnectorContextMenu(node, props)
const curValue = typeof cur.value === 'number' ? cur.value : 0
const nextValue = typeof next.value === 'number' ? next.value : 1
const curValue = props.isScalar ? (cur.value as number) : 0
const nextValue = props.isScalar ? (next.value as number) : 1
const leftYInExtremumSpace = props.extremumSpace.fromValueSpace(curValue)
const rightYInExtremumSpace = props.extremumSpace.fromValueSpace(nextValue)

View file

@ -68,12 +68,8 @@ const CurveHandle: React.FC<IProps> = (props) => {
const valInDiffSpace =
props.which === 'left' ? cur.handles[3] : next.handles[1]
if (!valInDiffSpace) {
// debugger
}
const curValue = typeof cur.value === 'number' ? cur.value : 0
const nextValue = typeof next.value === 'number' ? next.value : 1
const curValue = props.isScalar ? (cur.value as number) : 0
const nextValue = props.isScalar ? (next.value as number) : 1
const value = curValue + (nextValue - curValue) * valInDiffSpace
@ -168,8 +164,8 @@ function useOurDrags(node: SVGCircleElement | null, props: IProps): void {
const dYInValueSpace =
propsAtStartOfDrag.extremumSpace.deltaToValueSpace(dYInExtremumSpace)
const curValue = typeof cur.value === 'number' ? cur.value : 0
const nextValue = typeof next.value === 'number' ? next.value : 1
const curValue = props.isScalar ? (cur.value as number) : 0
const nextValue = props.isScalar ? (next.value as number) : 1
const dyInKeyframeDiffSpace = dYInValueSpace / (nextValue - curValue)
if (propsAtStartOfDrag.which === 'left') {

View file

@ -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,
},
)
})
},
},
]
},
})
}

View file

@ -51,7 +51,7 @@ const HitZone = styled.circle`
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 {index, trackData} = props
@ -59,12 +59,12 @@ const Dot: React.FC<IProps> = (props) => {
const next = trackData.keyframes[index + 1]
const [contextMenu] = useKeyframeContextMenu(node, props)
const isDragging =
typeof cur.value === 'number' ? useDragKeyframe(node, props) : false
const cyInExtremumSpace = props.extremumSpace.fromValueSpace(
typeof cur.value === 'number' ? cur.value : 0,
)
const curValue = cur.value as number
const isDragging = useDragKeyframe(node, props)
const cyInExtremumSpace = props.extremumSpace.fromValueSpace(curValue)
return (
<>
@ -93,7 +93,7 @@ const Dot: React.FC<IProps> = (props) => {
)
}
export default Dot
export default GraphEditorDotScalar
function useDragKeyframe(
node: SVGCircleElement | null,
@ -146,6 +146,7 @@ function useDragKeyframe(
value: (original.value as number) + dYInValueSpace,
handles: [...original.handles],
}
updatedKeyframes.push(cur)
if (keepSpeeds) {

View file

@ -12,7 +12,8 @@ import type {graphEditorColors} from '@theatre/studio/panels/SequenceEditorPanel
import type {ExtremumSpace} from '@theatre/studio/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/BasicKeyframedTrack'
import Curve from './Curve'
import CurveHandle from './CurveHandle'
import Dot from './Dot'
import GraphEditorDotScalar from './GraphEditorDotScalar'
import GraphEditorDotNonScalar from './GraphEditorDotNonScalar'
const Container = styled.g`
/* position: absolute; */
@ -28,9 +29,10 @@ const KeyframeEditor: React.FC<{
trackId: SequenceTrackId
sheetObject: SheetObject
extremumSpace: ExtremumSpace
isScalar: boolean
color: keyof typeof graphEditorColors
}> = (props) => {
const {index, trackData} = props
const {index, trackData, isScalar} = props
const cur = trackData.keyframes[index]
const next = trackData.keyframes[index + 1]
@ -48,7 +50,19 @@ const KeyframeEditor: React.FC<{
) : (
noConnector
)}
<Dot {...props} />
{isScalar ? (
<GraphEditorDotScalar {...props} />
) : (
<>
<GraphEditorDotNonScalar {...props} which="left" />
{shouldShowCurve && (
<GraphEditorDotNonScalar
{...{...props, index: index + 1, keyframe: next}}
which="right"
/>
)}
</>
)}
</Container>
)
}