Skip to content

Commit

Permalink
Fix aggregate keyframe dragging stopping in an edge case when the key…
Browse files Browse the repository at this point in the history
… for the drag element changes (theatre-js#189)

Co-authored-by: Cole Lawrence <[email protected]>
  • Loading branch information
AndrewPrifer and colelawrence authored Jun 8, 2022
1 parent 6b0b9f0 commit a90aee9
Show file tree
Hide file tree
Showing 4 changed files with 279 additions and 191 deletions.
Original file line number Diff line number Diff line change
@@ -1,32 +1,19 @@
import {val} from '@theatre/dataverse'
import React, {useMemo, useRef} from 'react'
import {AggregateKeyframePositionIsSelected} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregatedKeyframeTrack'
import React from 'react'
import useRefAndState from '@theatre/studio/utils/useRefAndState'
import type {UseDragOpts} from '@theatre/studio/uiComponents/useDrag'
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
import getStudio from '@theatre/studio/getStudio'
import useDrag from '@theatre/studio/uiComponents/useDrag'
import {useLogger} from '@theatre/studio/uiComponents/useLogger'
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
import {useCssCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler'
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
import type {IAggregateKeyframeEditorProps} from './AggregateKeyframeEditor'
import type {IAggregateKeyframeEditorUtils} from './useAggregateKeyframeEditorUtils'
import {AggregateKeyframeVisualDot, HitZone} from './AggregateKeyframeVisualDot'
import getStudio from '@theatre/studio/getStudio'
import {
copyableKeyframesFromSelection,
keyframesWithPaths,
} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/selections'
import type {KeyframeWithPathToPropFromCommonRoot} from '@theatre/studio/store/types/ahistoric'
import {commonRootOfPathsToProps} from '@theatre/shared/utils/addresses'
import type {ILogger} from '@theatre/shared/logger'
import {
collectKeyframeSnapPositions,
snapToNone,
snapToSome,
} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget'
import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'

type IAggregateKeyframeDotProps = {
editorProps: IAggregateKeyframeEditorProps
Expand All @@ -40,18 +27,17 @@ export function AggregateKeyframeDot(
const {cur} = props.utils

const [ref, node] = useRefAndState<HTMLDivElement | null>(null)
const [isDragging] = useDragForAggregateKeyframeDot(node, props, {
onClickFromDrag(dragStartEvent) {
// TODO Aggregate inline keyframe editor
// openEditor(dragStartEvent, ref.current!)
},
})

const [contextMenu] = useAggregateKeyframeContextMenu(props, logger, node)

return (
<>
<HitZone ref={ref} />
<HitZone
ref={ref}
// Need this for the dragging logic to be able to get the keyframe props
// based on the position.
{...DopeSnap.includePositionSnapAttrs(cur.position)}
/>
<AggregateKeyframeVisualDot
isAllHere={cur.allHere}
isSelected={cur.selected}
Expand Down Expand Up @@ -144,149 +130,3 @@ function useAggregateKeyframeContextMenu(
},
})
}

function useDragForAggregateKeyframeDot(
node: HTMLDivElement | null,
props: IAggregateKeyframeDotProps,
options: {
/**
* hmm: this is a hack so we can actually receive the
* {@link MouseEvent} from the drag event handler and use
* it for positioning the popup.
*/
onClickFromDrag(dragStartEvent: MouseEvent): void
},
): [isDragging: boolean] {
const propsRef = useRef(props.editorProps)
propsRef.current = props.editorProps
const keyframesRef = useRef(props.utils.cur.keyframes)
keyframesRef.current = props.utils.cur.keyframes

const useDragOpts = useMemo<UseDragOpts>(() => {
return {
debugName: 'AggregateKeyframeDot/useDragKeyframe',
onDragStart(event) {
const props = propsRef.current
const keyframes = keyframesRef.current

const tracksByObject = val(
getStudio()!.atomP.historic.coreByProject[
props.viewModel.sheetObject.address.projectId
].sheetsById[props.viewModel.sheetObject.address.sheetId].sequence
.tracksByObject,
)!

// Calculate all the valid snap positions in the sequence editor,
// excluding the child keyframes of this aggregate, and any selection it is part of.
const snapPositions = collectKeyframeSnapPositions(
tracksByObject,
function shouldIncludeKeyfram(keyframe, {trackId, objectKey}) {
return (
// we exclude all the child keyframes of this aggregate keyframe from being a snap target
keyframes.every(
(kfWithTrack) => keyframe.id !== kfWithTrack.kf.id,
) &&
!(
// if all of the children of the current aggregate keyframe are in a selection,
(
props.selection &&
// then we exclude them and all other keyframes in the selection from being snap targets
props.selection.byObjectKey[objectKey]?.byTrackId[trackId]
?.byKeyframeId[keyframe.id]
)
)
)
},
)

snapToSome(snapPositions)

if (
props.selection &&
props.aggregateKeyframes[props.index].selected ===
AggregateKeyframePositionIsSelected.AllSelected
) {
const {selection, viewModel} = props
const {sheetObject} = viewModel
const handlers = selection
.getDragHandlers({
...sheetObject.address,
domNode: node!,
positionAtStartOfDrag: keyframes[0].kf.position,
})
.onDragStart(event)

return (
handlers && {
...handlers,
onClick: options.onClickFromDrag,
onDragEnd: (...args) => {
handlers.onDragEnd?.(...args)
snapToNone()
},
}
)
}

const propsAtStartOfDrag = props
const toUnitSpace = val(
propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace,
)

let tempTransaction: CommitOrDiscard | undefined

return {
onDrag(dx, dy, event) {
const newPosition = Math.max(
// check if our event hoversover a [data-pos] element
DopeSnap.checkIfMouseEventSnapToPos(event, {
ignore: node,
}) ??
// if we don't find snapping target, check the distance dragged + original position
keyframes[0].kf.position + toUnitSpace(dx),
// sanitize to minimum of zero
0,
)

tempTransaction?.discard()
tempTransaction = undefined
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
for (const keyframe of keyframes) {
const original = keyframe.kf
stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes(
{
...propsAtStartOfDrag.viewModel.sheetObject.address,
trackId: keyframe.track.id,
keyframes: [{...original, position: newPosition}],
snappingFunction: val(
propsAtStartOfDrag.layoutP.sheet,
).getSequence().closestGridPosition,
},
)
}
})
},
onDragEnd(dragHappened) {
if (dragHappened) {
tempTransaction?.commit()
} else {
tempTransaction?.discard()
}

snapToNone()
},
onClick(ev) {
options.onClickFromDrag(ev)
},
}
},
}
}, [])

const [isDragging] = useDrag(node, useDragOpts)

useLockFrameStampPosition(isDragging, props.utils.cur.position)
useCssCursorLock(isDragging, 'draggingPositionInSequenceEditor', 'ew-resize')

return [isDragging]
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,26 @@ export function useAggregateKeyframeEditorUtils(
const {index, aggregateKeyframes, selection} = props
const sheetObjectAddress = props.viewModel.sheetObject.address

return usePrism(() => {
return usePrism(getAggregateKeyframeEditorUtilsPrismFn(props), [
index,
aggregateKeyframes,
selection,
sheetObjectAddress,
])
}

// I think this was pulled out for performance
// 1/10: Not sure this is properly split up
export function getAggregateKeyframeEditorUtilsPrismFn(
props: Pick<
IAggregateKeyframeEditorProps,
'index' | 'aggregateKeyframes' | 'selection' | 'viewModel'
>,
) {
const {index, aggregateKeyframes, selection} = props
const sheetObjectAddress = props.viewModel.sheetObject.address

return () => {
const cur = aggregateKeyframes[index]
const next = aggregateKeyframes[index + 1]

Expand Down Expand Up @@ -80,5 +99,5 @@ export function useAggregateKeyframeEditorUtils(
isAggregateEditingInCurvePopover,
allConnections,
}
}, [index, aggregateKeyframes, selection, sheetObjectAddress])
}
}
Loading

0 comments on commit a90aee9

Please sign in to comment.