All keyframe operations now snap to the frame grid

This commit is contained in:
Aria Minaei 2021-08-09 11:31:37 +02:00
parent 2ec6a54060
commit d46e0bfd2f
8 changed files with 51 additions and 17 deletions

View file

@ -97,11 +97,13 @@ export default class Sequence {
return this.closestGridPosition(this.position)
}
closestGridPosition(posInUnitSpace: number): number {
closestGridPosition = (posInUnitSpace: number): number => {
const subUnitsPerUnit = this.subUnitsPerUnit
const gridLength = 1 / subUnitsPerUnit
return Math.round(posInUnitSpace / gridLength) * gridLength
return parseFloat(
(Math.round(posInUnitSpace / gridLength) * gridLength).toFixed(3),
)
}
set position(requestedPosition: number) {

View file

@ -144,12 +144,14 @@ export default class StudioStore {
) as $FixMe as SequenceTrackId | undefined
if (typeof trackId === 'string') {
const seq = root.sheet.getSequence()
stateEditors.coreByProject.historic.sheetsById.sequence.setKeyframeAtPosition(
{
...propAddress,
trackId,
position: root.sheet.getSequence().position,
position: seq.position,
value: v as $FixMe,
snappingFunction: seq.closestGridPosition,
},
)
} else {
@ -196,7 +198,8 @@ export default class StudioStore {
{
...propAddress,
trackId,
position: root.sheet.getSequence().position,
position:
root.sheet.getSequence().positionSnappedToGrid,
},
)
} else {

View file

@ -14,6 +14,7 @@ import type {
} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
import {dotSize} from './Dot'
import type KeyframeEditor from './KeyframeEditor'
import type Sequence from '@theatre/core/sequences/Sequence'
const connectorHeight = dotSize / 2 + 1
const connectorWidthUnscaled = 1000
@ -108,6 +109,8 @@ const Connector: React.FC<IProps> = (props) => {
replaceKeyframes({
...props.leaf.sheetObject.address,
snappingFunction: val(props.layoutP.sheet).getSequence()
.closestGridPosition,
trackId: props.leaf.trackId,
keyframes: [
{
@ -157,6 +160,7 @@ function useDragKeyframe(node: HTMLDivElement | null, props: IProps) {
let selectionDragHandlers:
| ReturnType<DopeSheetSelection['getDragHandlers']>
| undefined
let sequence: Sequence
return {
lockCursorTo: 'ew-resize',
onDragStart(event) {
@ -174,6 +178,7 @@ function useDragKeyframe(node: HTMLDivElement | null, props: IProps) {
}
propsAtStartOfDrag = propsRef.current
sequence = val(propsAtStartOfDrag.layoutP.sheet).getSequence()
toUnitSpace = val(propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace)
},
@ -201,6 +206,7 @@ function useDragKeyframe(node: HTMLDivElement | null, props: IProps) {
translate: delta,
scale: 1,
origin: 0,
snappingFunction: sequence.closestGridPosition,
},
)
})

View file

@ -209,6 +209,9 @@ function useDragKeyframe(
...propsAtStartOfDrag.leaf.sheetObject.address,
trackId: propsAtStartOfDrag.leaf.trackId,
keyframes: [{...original, position: newPosition}],
snappingFunction: val(
propsAtStartOfDrag.layoutP.sheet,
).getSequence().closestGridPosition,
},
)
})

View file

@ -227,6 +227,7 @@ namespace utils {
translate: delta,
scale: 1,
origin: 0,
snappingFunction: sheet.getSequence().closestGridPosition,
})
}
}

View file

@ -175,6 +175,9 @@ function useOurDrags(node: SVGCircleElement | null, props: IProps): void {
stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes(
{
...propsAtStartOfDrag.sheetObject.address,
snappingFunction: val(
propsAtStartOfDrag.layoutP.sheet,
).getSequence().closestGridPosition,
trackId: propsAtStartOfDrag.trackId,
keyframes: [
{
@ -194,6 +197,9 @@ function useOurDrags(node: SVGCircleElement | null, props: IProps): void {
{
...propsAtStartOfDrag.sheetObject.address,
trackId: propsAtStartOfDrag.trackId,
snappingFunction: val(
propsAtStartOfDrag.layoutP.sheet,
).getSequence().closestGridPosition,
keyframes: [
{
...next,

View file

@ -198,6 +198,9 @@ function useDragKeyframe(
...propsAtStartOfDrag.sheetObject.address,
trackId: propsAtStartOfDrag.trackId,
keyframes: updatedKeyframes,
snappingFunction: val(
propsAtStartOfDrag.layoutP.sheet,
).getSequence().closestGridPosition,
},
)
})

View file

@ -458,11 +458,13 @@ namespace stateEditors {
trackId: string
position: number
value: number
snappingFunction: SnappingFunction
},
) {
const position = p.snappingFunction(p.position)
const {keyframes} = _getTrack(p)
const existingKeyframeIndex = keyframes.findIndex(
(kf) => kf.position === p.position,
(kf) => kf.position === position,
)
if (existingKeyframeIndex !== -1) {
const kf = keyframes[existingKeyframeIndex]
@ -471,12 +473,12 @@ namespace stateEditors {
}
const indexOfLeftKeyframe = findLastIndex(
keyframes,
(kf) => kf.position < p.position,
(kf) => kf.position < position,
)
if (indexOfLeftKeyframe === -1) {
keyframes.unshift({
id: generateKeyframeId(),
position: p.position,
position,
connectedRight: true,
handles: [0.5, 1, 0.5, 0],
value: p.value,
@ -486,7 +488,7 @@ namespace stateEditors {
const leftKeyframe = keyframes[indexOfLeftKeyframe]
keyframes.splice(indexOfLeftKeyframe + 1, 0, {
id: generateKeyframeId(),
position: p.position,
position,
connectedRight: leftKeyframe.connectedRight,
handles: [0.5, 1, 0.5, 0],
value: p.value,
@ -508,6 +510,8 @@ namespace stateEditors {
keyframes.splice(index, 1)
}
type SnappingFunction = (p: number) => number
export function transformKeyframes(
p: WithoutSheetInstance<SheetObjectAddress> & {
trackId: string
@ -515,6 +519,7 @@ namespace stateEditors {
translate: number
scale: number
origin: number
snappingFunction: SnappingFunction
},
) {
const track = _getTrack(p)
@ -527,7 +532,9 @@ namespace stateEditors {
const transformed = selectedKeyframes.map((untransformedKf) => {
const oldPosition = untransformedKf.position
const newPosition = transformNumber(oldPosition, p)
const newPosition = p.snappingFunction(
transformNumber(oldPosition, p),
)
return {...untransformedKf, position: newPosition}
})
@ -551,17 +558,20 @@ namespace stateEditors {
p: WithoutSheetInstance<SheetObjectAddress> & {
trackId: string
keyframes: Array<Keyframe>
snappingFunction: SnappingFunction
},
) {
const track = _getTrack(p)
const initialKeyframes = current(track.keyframes)
const sanitizedKeyframes = p.keyframes.filter((kf) => {
if (!isFinite(kf.value)) return false
if (!kf.handles.every((handleValue) => isFinite(handleValue)))
return false
const sanitizedKeyframes = p.keyframes
.filter((kf) => {
if (!isFinite(kf.value)) return false
if (!kf.handles.every((handleValue) => isFinite(handleValue)))
return false
return true
})
return true
})
.map((kf) => ({...kf, position: p.snappingFunction(kf.position)}))
const newKeyframesById = keyBy(sanitizedKeyframes, 'id')
@ -569,13 +579,13 @@ namespace stateEditors {
(kf) => !newKeyframesById[kf.id],
)
const unselectedByPositino = keyBy(unselected, 'position')
const unselectedByPosition = keyBy(unselected, 'position')
// If the new transformed keyframes overlap with any existing keyframes,
// we remove the overlapped keyframes
sanitizedKeyframes.forEach(({position}) => {
const existingKeyframeAtThisPosition =
unselectedByPositino[position]
unselectedByPosition[position]
if (existingKeyframeAtThisPosition) {
pullFromArray(unselected, existingKeyframeAtThisPosition)
}