diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeDot.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeDot.tsx
index 43703df..c718942 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeDot.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeDot.tsx
@@ -1,7 +1,6 @@
import {val} from '@theatre/dataverse'
import React, {useMemo, useRef} from 'react'
import {AggregateKeyframePositionIsSelected} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregatedKeyframeTrack'
-import {DopeSnapHitZoneUI} from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnapHitZoneUI'
import useRefAndState from '@theatre/studio/utils/useRefAndState'
import type {UseDragOpts} from '@theatre/studio/uiComponents/useDrag'
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
@@ -21,6 +20,13 @@ import {
} 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'
type IAggregateKeyframeDotProps = {
editorProps: IAggregateKeyframeEditorProps
@@ -41,17 +47,11 @@ export function AggregateKeyframeDot(
},
})
- const [contextMenu] = useAggregateKeyframeContextMenu(props, node)
+ const [contextMenu] = useAggregateKeyframeContextMenu(props, logger, node)
return (
<>
-
+
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 ===
@@ -172,7 +208,7 @@ function useDragForAggregateKeyframeDot(
) {
const {selection, viewModel} = props
const {sheetObject} = viewModel
- const hanlders = selection
+ const handlers = selection
.getDragHandlers({
...sheetObject.address,
domNode: node!,
@@ -180,7 +216,16 @@ function useDragForAggregateKeyframeDot(
})
.onDragStart(event)
- return hanlders && {...hanlders, onClick: options.onClickFromDrag}
+ return (
+ handlers && {
+ ...handlers,
+ onClick: options.onClickFromDrag,
+ onDragEnd: (...args) => {
+ handlers.onDragEnd?.(...args)
+ snapToNone()
+ },
+ }
+ )
}
const propsAtStartOfDrag = props
@@ -227,6 +272,8 @@ function useDragForAggregateKeyframeDot(
} else {
tempTransaction?.discard()
}
+
+ snapToNone()
},
onClick(ev) {
options.onClickFromDrag(ev)
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeVisualDot.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeVisualDot.tsx
index 1278745..19153c5 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeVisualDot.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregateKeyframeEditor/AggregateKeyframeVisualDot.tsx
@@ -2,7 +2,7 @@ import React from 'react'
import {AggregateKeyframePositionIsSelected} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregatedKeyframeTrack'
import styled from 'styled-components'
import {absoluteDims} from '@theatre/studio/utils/absoluteDims'
-import {DopeSnapHitZoneUI} from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnapHitZoneUI'
+import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
const DOT_SIZE_PX = 16
const DOT_HOVER_SIZE_PX = DOT_SIZE_PX + 5
@@ -20,16 +20,15 @@ export const HitZone = styled.div`
z-index: 2;
cursor: ew-resize;
- ${DopeSnapHitZoneUI.CSS}
+ position: absolute;
+ ${absoluteDims(12)};
+ ${pointerEventsAutoInNormalMode};
- #pointer-root.draggingPositionInSequenceEditor & {
- ${DopeSnapHitZoneUI.CSS_WHEN_SOMETHING_DRAGGING}
- }
-
- &:hover + ${DotContainer},
- #pointer-root.draggingPositionInSequenceEditor &:hover + ${DotContainer},
- // notice "," css "or"
- &.${DopeSnapHitZoneUI.BEING_DRAGGED_CLASS} + ${DotContainer} {
+ &:hover
+ + ${DotContainer},
+ #pointer-root.draggingPositionInSequenceEditor
+ &:hover
+ + ${DotContainer} {
${absoluteDims(DOT_HOVER_SIZE_PX)}
}
`
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregatedKeyframeTrack.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregatedKeyframeTrack.tsx
index 2026e6c..94072a4 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregatedKeyframeTrack.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/AggregatedKeyframeTrack/AggregatedKeyframeTrack.tsx
@@ -7,11 +7,11 @@ import type {
SequenceEditorTree_SheetObject,
} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
-import {usePrism} from '@theatre/react'
+import {usePrism, useVal} from '@theatre/react'
import type {Pointer} from '@theatre/dataverse'
import {valueDerivation} from '@theatre/dataverse'
import {val} from '@theatre/dataverse'
-import React from 'react'
+import React, {Fragment, useMemo} from 'react'
import styled from 'styled-components'
import type {IContextMenuItem} from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
@@ -21,16 +21,20 @@ import AggregateKeyframeEditor from './AggregateKeyframeEditor/AggregateKeyframe
import type {AggregatedKeyframes} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes'
import {useLogger} from '@theatre/studio/uiComponents/useLogger'
import getStudio from '@theatre/studio/getStudio'
-import type {
- SheetObjectAddress} from '@theatre/shared/utils/addresses';
+import type {SheetObjectAddress} from '@theatre/shared/utils/addresses'
import {
decodePathToProp,
doesPathStartWith,
- encodePathToProp
+ encodePathToProp,
} from '@theatre/shared/utils/addresses'
import type {SequenceTrackId} from '@theatre/shared/utils/ids'
import type Sequence from '@theatre/core/sequences/Sequence'
import type {KeyframeWithPathToPropFromCommonRoot} from '@theatre/studio/store/types'
+import {collectAggregateSnapPositions} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes'
+import KeyframeSnapTarget, {
+ snapPositionsStateD,
+} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget'
+import {emptyObject} from '@theatre/shared/utils'
const AggregatedKeyframeTrackContainer = styled.div`
position: relative;
@@ -88,21 +92,50 @@ function AggregatedKeyframeTrack_memo(props: IAggregatedKeyframeTracksProps) {
}),
)
- const keyframeEditors = posKfs.map(({position, keyframes}, index) => (
- collectAggregateSnapPositions(viewModel, snapPositions),
+ [snapPositions],
+ )
+
+ const snapTargets = aggregateSnapPositions.map((position) => (
+
))
+ const keyframeEditors = posKfs.map(({position, keyframes}, index) => (
+
+ {snapToAllKeyframes && (
+
+ )}
+
+
+ ))
+
return (
{keyframeEditors}
+ {snapTargets}
{contextMenu}
)
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/BasicKeyframedTrack.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/BasicKeyframedTrack.tsx
index fbf4411..7bf6fc7 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/BasicKeyframedTrack.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/BasicKeyframedTrack.tsx
@@ -2,10 +2,10 @@ import type {TrackData} from '@theatre/core/projects/store/types/SheetState_Hist
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
import type {SequenceEditorTree_PrimitiveProp} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
-import {usePrism} from '@theatre/react'
+import {usePrism, useVal} from '@theatre/react'
import type {Pointer} from '@theatre/dataverse'
import {val} from '@theatre/dataverse'
-import React from 'react'
+import React, {Fragment} from 'react'
import styled from 'styled-components'
import SingleKeyframeEditor from './KeyframeEditor/SingleKeyframeEditor'
import type {IContextMenuItem} from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
@@ -14,6 +14,9 @@ import useRefAndState from '@theatre/studio/utils/useRefAndState'
import getStudio from '@theatre/studio/getStudio'
import {arePathsEqual} from '@theatre/shared/utils/addresses'
import type {KeyframeWithPathToPropFromCommonRoot} from '@theatre/studio/store/types'
+import KeyframeSnapTarget, {
+ snapPositionsStateD,
+} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget'
const Container = styled.div`
position: relative;
@@ -58,15 +61,45 @@ const BasicKeyframedTrack: React.VFC = React.memo(
props,
)
+ const snapPositionsState = useVal(snapPositionsStateD)
+
+ const snapPositions =
+ snapPositionsState.mode === 'snapToSome'
+ ? snapPositionsState.positions[leaf.sheetObject.address.objectKey]?.[
+ leaf.trackId
+ ]
+ : [] ?? []
+
+ const snapToAllKeyframes = snapPositionsState.mode === 'snapToAll'
+
const keyframeEditors = trackData.keyframes.map((kf, index) => (
-
+ {snapToAllKeyframes && (
+
+ )}
+
+
+ ))
+
+ const snapTargets = snapPositions.map((position) => (
+
))
@@ -78,6 +111,7 @@ const BasicKeyframedTrack: React.VFC = React.memo(
}}
>
{keyframeEditors}
+ {snapTargets}
{contextMenu}
)
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/SingleKeyframeDot.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/SingleKeyframeDot.tsx
index e472e08..557df7e 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/SingleKeyframeDot.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/SingleKeyframeDot.tsx
@@ -5,8 +5,8 @@ import last from 'lodash-es/last'
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 type {UseDragOpts} from '@theatre/studio/uiComponents/useDrag'
+import useDrag from '@theatre/studio/uiComponents/useDrag'
import useRefAndState from '@theatre/studio/utils/useRefAndState'
import {val} from '@theatre/dataverse'
import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
@@ -19,10 +19,15 @@ import {useTempTransactionEditingTools} from './useTempTransactionEditingTools'
import {DeterminePropEditorForSingleKeyframe} from './DeterminePropEditorForSingleKeyframe'
import type {ISingleKeyframeEditorProps} from './SingleKeyframeEditor'
import {absoluteDims} from '@theatre/studio/utils/absoluteDims'
-import {DopeSnapHitZoneUI} from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnapHitZoneUI'
import {useLogger} from '@theatre/studio/uiComponents/useLogger'
import type {ILogger} from '@theatre/shared/logger'
import {copyableKeyframesFromSelection} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/selections'
+import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
+import {
+ collectKeyframeSnapPositions,
+ snapToNone,
+ snapToSome,
+} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget'
export const DOT_SIZE_PX = 6
const DOT_HOVER_SIZE_PX = DOT_SIZE_PX + 5
@@ -49,17 +54,11 @@ const HitZone = styled.div`
z-index: 1;
cursor: ew-resize;
- ${DopeSnapHitZoneUI.CSS}
+ position: absolute;
+ ${absoluteDims(12)};
+ ${pointerEventsAutoInNormalMode};
- #pointer-root.draggingPositionInSequenceEditor & {
- ${DopeSnapHitZoneUI.CSS_WHEN_SOMETHING_DRAGGING}
- }
-
- &:hover
- + ${Diamond},
- // notice , "or" in CSS
- &.${DopeSnapHitZoneUI.BEING_DRAGGED_CLASS}
- + ${Diamond} {
+ &:hover + ${Diamond} {
${absoluteDims(DOT_HOVER_SIZE_PX)}
}
`
@@ -82,13 +81,7 @@ const SingleKeyframeDot: React.VFC = (props) => {
return (
<>
-
+
{inlineEditorPopover}
{contextMenu}
@@ -210,6 +203,37 @@ function useDragForSingleKeyframeDot(
debugName: 'KeyframeDot/useDragKeyframe',
onDragStart(event) {
const props = propsRef.current
+
+ const tracksByObject = val(
+ getStudio()!.atomP.historic.coreByProject[
+ props.leaf.sheetObject.address.projectId
+ ].sheetsById[props.leaf.sheetObject.address.sheetId].sequence
+ .tracksByObject,
+ )!
+
+ const snapPositions = collectKeyframeSnapPositions(
+ tracksByObject,
+ // Calculate all the valid snap positions in the sequence editor,
+ // excluding this keyframe, and any selection it is part of.
+ function shouldIncludeKeyfram(keyframe, {trackId, objectKey}) {
+ return (
+ // we exclude this keyframe from being a snap target
+ keyframe.id !== props.keyframe.id &&
+ !(
+ // if the current dragged keyframe is in the selection,
+ (
+ props.selection &&
+ // then we exclude it 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) {
const {selection, leaf} = props
const {sheetObject} = leaf
@@ -226,7 +250,16 @@ function useDragForSingleKeyframeDot(
// in the future, we may want to show an multi-editor, like in the
// single tween editor, so that selected keyframes' values can be changed
// together
- return handlers && {...handlers, onClick: options.onClickFromDrag}
+ return (
+ handlers && {
+ ...handlers,
+ onClick: options.onClickFromDrag,
+ onDragEnd: (...args) => {
+ handlers.onDragEnd?.(...args)
+ snapToNone()
+ },
+ }
+ )
}
const propsAtStartOfDrag = props
@@ -272,6 +305,8 @@ function useDragForSingleKeyframeDot(
} else {
tempTransaction?.discard()
}
+
+ snapToNone()
},
onClick(ev) {
options.onClickFromDrag(ev)
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/HorizontallyScrollableArea.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/HorizontallyScrollableArea.tsx
index feed734..5168fc7 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/HorizontallyScrollableArea.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/HorizontallyScrollableArea.tsx
@@ -12,6 +12,7 @@ import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
import {useCssCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler'
import type {IRange} from '@theatre/shared/utils/types'
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
+import {snapToAll, snapToNone} from './KeyframeSnapTarget'
const Container = styled.div`
position: absolute;
@@ -115,6 +116,8 @@ function useDragPlayheadHandlers(
const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace)
setIsSeeking(true)
+ snapToAll()
+
return {
onDrag(dx: number, _, event) {
const deltaPos = scaledSpaceToUnitSpace(dx)
@@ -135,6 +138,7 @@ function useDragPlayheadHandlers(
},
onDragEnd() {
setIsSeeking(false)
+ snapToNone()
},
}
},
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget.tsx
new file mode 100644
index 0000000..fad2c44
--- /dev/null
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget.tsx
@@ -0,0 +1,129 @@
+import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
+import type {Pointer} from '@theatre/dataverse'
+import {Box, val} from '@theatre/dataverse'
+import React from 'react'
+import styled from 'styled-components'
+import {DopeSnapHitZoneUI} from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnapHitZoneUI'
+import type {ObjectAddressKey, SequenceTrackId} from '@theatre/shared/utils/ids'
+import type {
+ BasicKeyframedTrack,
+ HistoricPositionalSequence,
+ Keyframe,
+} from '@theatre/core/projects/store/types/SheetState_Historic'
+
+const HitZone = styled.div`
+ z-index: 1;
+ cursor: ew-resize;
+
+ ${DopeSnapHitZoneUI.CSS}
+
+ #pointer-root.draggingPositionInSequenceEditor & {
+ ${DopeSnapHitZoneUI.CSS_WHEN_SOMETHING_DRAGGING}
+ }
+`
+
+const Container = styled.div`
+ position: absolute;
+`
+
+export type ISnapTargetPRops = {
+ layoutP: Pointer
+ leaf: {nodeHeight: number}
+ position: number
+}
+
+const KeyframeSnapTarget: React.VFC = (props) => {
+ return (
+
+
+
+ )
+}
+
+export default KeyframeSnapTarget
+
+export type KeyframeSnapPositions = {
+ [objectKey: ObjectAddressKey]: {
+ [trackId: SequenceTrackId]: number[]
+ }
+}
+
+const stateB = new Box<
+ | {
+ // all keyframes must be snap targets
+ mode: 'snapToAll'
+ }
+ | {
+ // only these keyframes must be snap targets
+ mode: 'snapToSome'
+ positions: KeyframeSnapPositions
+ }
+ | {
+ // no keyframe should be a snap target
+ mode: 'snapToNone'
+ }
+>({mode: 'snapToNone'})
+
+export const snapPositionsStateD = stateB.derivation
+
+export function snapToAll() {
+ stateB.set({mode: 'snapToAll'})
+}
+
+export function snapToNone() {
+ stateB.set({mode: 'snapToNone'})
+}
+
+export function snapToSome(positions: KeyframeSnapPositions) {
+ stateB.set({mode: 'snapToSome', positions})
+}
+
+export function collectKeyframeSnapPositions(
+ tracksByObject: HistoricPositionalSequence['tracksByObject'],
+ shouldIncludeKeyframe: (
+ kf: Keyframe,
+ track: {
+ trackId: SequenceTrackId
+ trackData: BasicKeyframedTrack
+ objectKey: ObjectAddressKey
+ },
+ ) => boolean,
+): KeyframeSnapPositions {
+ return Object.fromEntries(
+ Object.entries(tracksByObject).map(
+ ([objectKey, trackDataAndTrackIdByPropPath]) => [
+ objectKey,
+ Object.fromEntries(
+ Object.entries(trackDataAndTrackIdByPropPath!.trackData).map(
+ ([trackId, track]) => [
+ trackId,
+ track!.keyframes
+ .filter((kf) =>
+ shouldIncludeKeyframe(kf, {
+ trackId,
+ trackData: track!,
+ objectKey,
+ }),
+ )
+ .map((keyframe) => keyframe.position),
+ ],
+ ),
+ ),
+ ],
+ ),
+ )
+}
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes.tsx
index 5ca3e89..a9e1af5 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/collectAggregateKeyframes.tsx
@@ -11,6 +11,7 @@ import type {
} from '@theatre/core/projects/store/types/SheetState_Historic'
import type {IUtilLogger} from '@theatre/shared/logger'
import {encodePathToProp} from '@theatre/shared/utils/addresses'
+import {uniq} from 'lodash-es'
/**
* An index over a series of keyframes that have been collected from different tracks.
@@ -128,3 +129,45 @@ export function collectAggregateKeyframesInPrism(
tracks,
}
}
+
+/**
+ * Collects all the snap positions for an aggregate track.
+ */
+export function collectAggregateSnapPositions(
+ leaf: SequenceEditorTree_PropWithChildren | SequenceEditorTree_SheetObject,
+ snapTargetPositions: {[key: string]: {[key: string]: number[]}},
+): number[] {
+ const sheetObject = leaf.sheetObject
+
+ const projectId = sheetObject.address.projectId
+
+ const sheetObjectTracksP =
+ getStudio().atomP.historic.coreByProject[projectId].sheetsById[
+ sheetObject.address.sheetId
+ ].sequence.tracksByObject[sheetObject.address.objectKey]
+
+ const positions: number[] = []
+ for (const childLeaf of leaf.children) {
+ if (childLeaf.type === 'primitiveProp') {
+ const trackId = val(
+ sheetObjectTracksP.trackIdByPropPath[
+ encodePathToProp(childLeaf.pathToProp)
+ ],
+ )
+ if (!trackId) {
+ continue
+ }
+
+ positions.push(
+ ...(snapTargetPositions[sheetObject.address.objectKey]?.[trackId] ??
+ []),
+ )
+ } else if (childLeaf.type === 'propWithChildren') {
+ positions.push(
+ ...collectAggregateSnapPositions(childLeaf, snapTargetPositions),
+ )
+ }
+ }
+
+ return uniq(positions)
+}
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/MarkerDot.tsx b/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/MarkerDot.tsx
index bd39883..55fb237 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/MarkerDot.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/MarkerDot.tsx
@@ -22,6 +22,10 @@ import {zIndexes} from '@theatre/studio/panels/SequenceEditorPanel/SequenceEdito
import DopeSnap from './DopeSnap'
import {absoluteDims} from '@theatre/studio/utils/absoluteDims'
import {DopeSnapHitZoneUI} from './DopeSnapHitZoneUI'
+import {
+ snapToAll,
+ snapToNone,
+} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget'
const MARKER_SIZE_W_PX = 12
const MARKER_SIZE_H_PX = 12
@@ -221,6 +225,8 @@ function useDragMarker(
const toUnitSpace = val(props.layoutP.scaledSpace.toUnitSpace)
let tempTransaction: CommitOrDiscard | undefined
+ snapToAll()
+
return {
onDrag(dx, _dy, event) {
const original = markerAtStartOfDrag
@@ -250,6 +256,8 @@ function useDragMarker(
onDragEnd(dragHappened) {
if (dragHappened) tempTransaction?.commit()
else tempTransaction?.discard()
+
+ snapToNone()
},
}
},
diff --git a/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/Playhead.tsx b/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/Playhead.tsx
index f1b271e..6af70b5 100644
--- a/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/Playhead.tsx
+++ b/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/Playhead.tsx
@@ -27,6 +27,10 @@ import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useCo
import getStudio from '@theatre/studio/getStudio'
import {generateSequenceMarkerId} from '@theatre/shared/utils/ids'
import DopeSnap from './DopeSnap'
+import {
+ snapToAll,
+ snapToNone,
+} from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/KeyframeSnapTarget'
const Container = styled.div<{isVisible: boolean}>`
--thumbColor: #00e0ff;
@@ -214,6 +218,8 @@ const Playhead: React.FC<{layoutP: Pointer}> = ({
const setIsSeeking = val(layoutP.seeker.setIsSeeking)
setIsSeeking(true)
+ snapToAll()
+
return {
onDrag(dx, _, event) {
const deltaPos = scaledSpaceToUnitSpace(dx)
@@ -227,6 +233,7 @@ const Playhead: React.FC<{layoutP: Pointer}> = ({
},
onDragEnd(dragHappened) {
setIsSeeking(false)
+ snapToNone()
},
onClick(e) {
openPopover(e, thumbRef.current!)