Perfect snapping behavior for all snap targets (#203)

Co-authored-by: Andrew Prifer <AndrewPrifer@users.noreply.github.com>
This commit is contained in:
Andrew Prifer 2022-06-08 12:55:55 +02:00 committed by GitHub
parent 3b3a1b1d8a
commit b323588d78
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 405 additions and 65 deletions

View file

@ -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 (
<>
<HitZone
ref={ref}
{...DopeSnapHitZoneUI.reactProps({
isDragging,
position: cur.position,
})}
/>
<HitZone ref={ref} />
<AggregateKeyframeVisualDot
isAllHere={cur.allHere}
isSelected={cur.selected}
@ -63,6 +63,7 @@ export function AggregateKeyframeDot(
function useAggregateKeyframeContextMenu(
props: IAggregateKeyframeDotProps,
logger: ILogger,
target: HTMLDivElement | null,
) {
return useContextMenu(target, {
@ -138,6 +139,9 @@ function useAggregateKeyframeContextMenu(
},
]
},
onOpen() {
logger._debug('Show aggregate keyframe', props)
},
})
}
@ -165,6 +169,38 @@ function useDragForAggregateKeyframeDot(
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 ===
@ -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)

View file

@ -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)}
}
`

View file

@ -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) => (
<AggregateKeyframeEditor
index={index}
const snapPositionsState = useVal(snapPositionsStateD)
const snapToAllKeyframes = snapPositionsState.mode === 'snapToAll'
const snapPositions =
snapPositionsState.mode === 'snapToSome'
? snapPositionsState.positions
: emptyObject
const aggregateSnapPositions = useMemo(
() => collectAggregateSnapPositions(viewModel, snapPositions),
[snapPositions],
)
const snapTargets = aggregateSnapPositions.map((position) => (
<KeyframeSnapTarget
key={'snap-target-' + position}
layoutP={layoutP}
viewModel={viewModel}
aggregateKeyframes={posKfs}
// To ensure that while dragging, we don't lose reference to the
// aggregate we're trying to drag.
key={'agg-' + keyframes[0].kf.id}
selection={
selectedPositions.has(position) === true ? selection : undefined
}
leaf={viewModel}
position={position}
/>
))
const keyframeEditors = posKfs.map(({position, keyframes}, index) => (
<Fragment key={'agg-' + keyframes[0].kf.id}>
{snapToAllKeyframes && (
<KeyframeSnapTarget
layoutP={layoutP}
leaf={viewModel}
position={position}
/>
)}
<AggregateKeyframeEditor
index={index}
layoutP={layoutP}
viewModel={viewModel}
aggregateKeyframes={posKfs}
selection={
selectedPositions.has(position) === true ? selection : undefined
}
/>
</Fragment>
))
return (
<AggregatedKeyframeTrackContainer
ref={containerRef}
@ -111,6 +144,7 @@ function AggregatedKeyframeTrack_memo(props: IAggregatedKeyframeTracksProps) {
}}
>
{keyframeEditors}
{snapTargets}
{contextMenu}
</AggregatedKeyframeTrackContainer>
)

View file

@ -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<BasicKeyframedTracksProps> = 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) => (
<SingleKeyframeEditor
keyframe={kf}
index={index}
trackData={trackData}
<Fragment key={'keyframe-' + kf.id}>
{snapToAllKeyframes && (
<KeyframeSnapTarget
layoutP={layoutP}
leaf={leaf}
position={kf.position}
/>
)}
<SingleKeyframeEditor
keyframe={kf}
index={index}
trackData={trackData}
layoutP={layoutP}
leaf={leaf}
selection={
selectedKeyframeIds[kf.id] === true ? selection : undefined
}
/>
</Fragment>
))
const snapTargets = snapPositions.map((position) => (
<KeyframeSnapTarget
key={'snap-target-' + position}
layoutP={layoutP}
leaf={leaf}
key={'keyframe-' + kf.id}
selection={selectedKeyframeIds[kf.id] === true ? selection : undefined}
position={position}
/>
))
@ -78,6 +111,7 @@ const BasicKeyframedTrack: React.VFC<BasicKeyframedTracksProps> = React.memo(
}}
>
{keyframeEditors}
{snapTargets}
{contextMenu}
</Container>
)

View file

@ -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<ISingleKeyframeDotProps> = (props) => {
return (
<>
<HitZone
ref={ref}
{...DopeSnapHitZoneUI.reactProps({
isDragging,
position: props.keyframe.position,
})}
/>
<HitZone ref={ref} />
<Diamond isSelected={!!props.selection} />
{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)

View file

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

View file

@ -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<SequenceEditorPanelLayout>
leaf: {nodeHeight: number}
position: number
}
const KeyframeSnapTarget: React.VFC<ISnapTargetPRops> = (props) => {
return (
<Container
style={{
top: `${props.leaf.nodeHeight / 2}px`,
left: `calc(${val(
props.layoutP.scaledSpace.leftPadding,
)}px + calc(var(--unitSpaceToScaledSpaceMultiplier) * ${
props.position
}px))`,
}}
>
<HitZone
{...DopeSnapHitZoneUI.reactProps({
isDragging: false,
position: props.position,
})}
/>
</Container>
)
}
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),
],
),
),
],
),
)
}

View file

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

View file

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

View file

@ -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<SequenceEditorPanelLayout>}> = ({
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<SequenceEditorPanelLayout>}> = ({
},
onDragEnd(dragHappened) {
setIsSeeking(false)
snapToNone()
},
onClick(e) {
openPopover(e, thumbRef.current!)