Simplify useDrag()
's API and implement middle-button dragging in SequenceEditor
This commit is contained in:
parent
e12d495f29
commit
202b61c48c
17 changed files with 995 additions and 972 deletions
|
@ -21,52 +21,44 @@ const PanelDragZone: React.FC<
|
||||||
const [ref, node] = useRefAndState<HTMLDivElement>(null as $IntentionalAny)
|
const [ref, node] = useRefAndState<HTMLDivElement>(null as $IntentionalAny)
|
||||||
|
|
||||||
const dragOpts: Parameters<typeof useDrag>[1] = useMemo(() => {
|
const dragOpts: Parameters<typeof useDrag>[1] = useMemo(() => {
|
||||||
let stuffBeforeDrag = panelStuffRef.current
|
|
||||||
let tempTransaction: CommitOrDiscard | undefined
|
|
||||||
let unlock: VoidFn | undefined
|
|
||||||
return {
|
return {
|
||||||
debugName: 'PanelDragZone',
|
debugName: 'PanelDragZone',
|
||||||
lockCursorTo: 'move',
|
lockCursorTo: 'move',
|
||||||
onDragStart() {
|
onDragStart() {
|
||||||
stuffBeforeDrag = panelStuffRef.current
|
const stuffBeforeDrag = panelStuffRef.current
|
||||||
if (unlock) {
|
let tempTransaction: CommitOrDiscard | undefined
|
||||||
const u = unlock
|
|
||||||
unlock = undefined
|
|
||||||
u()
|
|
||||||
}
|
|
||||||
unlock = panelStuff.addBoundsHighlightLock()
|
|
||||||
},
|
|
||||||
onDrag(dx, dy) {
|
|
||||||
const newDims: typeof panelStuff['dims'] = {
|
|
||||||
...stuffBeforeDrag.dims,
|
|
||||||
top: stuffBeforeDrag.dims.top + dy,
|
|
||||||
left: stuffBeforeDrag.dims.left + dx,
|
|
||||||
}
|
|
||||||
const position = panelDimsToPanelPosition(newDims, {
|
|
||||||
width: window.innerWidth,
|
|
||||||
height: window.innerHeight,
|
|
||||||
})
|
|
||||||
|
|
||||||
tempTransaction?.discard()
|
const unlock = panelStuff.addBoundsHighlightLock()
|
||||||
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
|
||||||
stateEditors.studio.historic.panelPositions.setPanelPosition({
|
return {
|
||||||
position,
|
onDrag(dx, dy) {
|
||||||
panelId: stuffBeforeDrag.panelId,
|
const newDims: typeof panelStuff['dims'] = {
|
||||||
})
|
...stuffBeforeDrag.dims,
|
||||||
})
|
top: stuffBeforeDrag.dims.top + dy,
|
||||||
},
|
left: stuffBeforeDrag.dims.left + dx,
|
||||||
onDragEnd(dragHappened) {
|
}
|
||||||
if (unlock) {
|
const position = panelDimsToPanelPosition(newDims, {
|
||||||
const u = unlock
|
width: window.innerWidth,
|
||||||
unlock = undefined
|
height: window.innerHeight,
|
||||||
u()
|
})
|
||||||
|
|
||||||
|
tempTransaction?.discard()
|
||||||
|
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
||||||
|
stateEditors.studio.historic.panelPositions.setPanelPosition({
|
||||||
|
position,
|
||||||
|
panelId: stuffBeforeDrag.panelId,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onDragEnd(dragHappened) {
|
||||||
|
unlock()
|
||||||
|
if (dragHappened) {
|
||||||
|
tempTransaction?.commit()
|
||||||
|
} else {
|
||||||
|
tempTransaction?.discard()
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if (dragHappened) {
|
|
||||||
tempTransaction?.commit()
|
|
||||||
} else {
|
|
||||||
tempTransaction?.discard()
|
|
||||||
}
|
|
||||||
tempTransaction = undefined
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
||||||
import type {$IntentionalAny, VoidFn} from '@theatre/shared/utils/types'
|
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
||||||
import getStudio from '@theatre/studio/getStudio'
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
|
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
|
||||||
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
||||||
import {lighten} from 'polished'
|
import {lighten} from 'polished'
|
||||||
import React, {useMemo, useRef, useState} from 'react'
|
import React, {useMemo, useRef} from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import {panelDimsToPanelPosition, usePanel} from './BasePanel'
|
import {panelDimsToPanelPosition, usePanel} from './BasePanel'
|
||||||
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||||
|
@ -142,96 +142,84 @@ const PanelResizeHandle: React.FC<{
|
||||||
const panelStuff = usePanel()
|
const panelStuff = usePanel()
|
||||||
const panelStuffRef = useRef(panelStuff)
|
const panelStuffRef = useRef(panelStuff)
|
||||||
panelStuffRef.current = panelStuff
|
panelStuffRef.current = panelStuff
|
||||||
const [isDragging, setIsDragging] = useState(false)
|
|
||||||
|
|
||||||
const [ref, node] = useRefAndState<HTMLDivElement>(null as $IntentionalAny)
|
const [ref, node] = useRefAndState<HTMLDivElement>(null as $IntentionalAny)
|
||||||
const dragOpts: Parameters<typeof useDrag>[1] = useMemo(() => {
|
const dragOpts: Parameters<typeof useDrag>[1] = useMemo(() => {
|
||||||
let stuffBeforeDrag = panelStuffRef.current
|
|
||||||
let tempTransaction: CommitOrDiscard | undefined
|
|
||||||
let unlock: VoidFn | undefined
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
debugName: 'PanelResizeHandle',
|
debugName: 'PanelResizeHandle',
|
||||||
lockCursorTo: cursors[which],
|
lockCursorTo: cursors[which],
|
||||||
onDragStart() {
|
onDragStart() {
|
||||||
stuffBeforeDrag = panelStuffRef.current
|
let tempTransaction: CommitOrDiscard | undefined
|
||||||
setIsDragging(true)
|
|
||||||
if (unlock) {
|
|
||||||
const u = unlock
|
|
||||||
unlock = undefined
|
|
||||||
u()
|
|
||||||
}
|
|
||||||
unlock = panelStuff.addBoundsHighlightLock()
|
|
||||||
},
|
|
||||||
onDrag(dx, dy) {
|
|
||||||
const newDims: typeof panelStuff['dims'] = {
|
|
||||||
...stuffBeforeDrag.dims,
|
|
||||||
}
|
|
||||||
|
|
||||||
if (which.startsWith('Bottom')) {
|
const stuffBeforeDrag = panelStuffRef.current
|
||||||
newDims.height = Math.max(
|
const unlock = panelStuff.addBoundsHighlightLock()
|
||||||
newDims.height + dy,
|
|
||||||
stuffBeforeDrag.minDims.height,
|
|
||||||
)
|
|
||||||
} else if (which.startsWith('Top')) {
|
|
||||||
const bottom = newDims.top + newDims.height
|
|
||||||
const top = Math.min(
|
|
||||||
bottom - stuffBeforeDrag.minDims.height,
|
|
||||||
newDims.top + dy,
|
|
||||||
)
|
|
||||||
const height = bottom - top
|
|
||||||
|
|
||||||
newDims.height = height
|
return {
|
||||||
newDims.top = top
|
onDrag(dx, dy) {
|
||||||
}
|
const newDims: typeof panelStuff['dims'] = {
|
||||||
if (which.endsWith('Left')) {
|
...stuffBeforeDrag.dims,
|
||||||
const right = newDims.left + newDims.width
|
}
|
||||||
const left = Math.min(
|
|
||||||
right - stuffBeforeDrag.minDims.width,
|
|
||||||
newDims.left + dx,
|
|
||||||
)
|
|
||||||
const width = right - left
|
|
||||||
|
|
||||||
newDims.width = width
|
if (which.startsWith('Bottom')) {
|
||||||
newDims.left = left
|
newDims.height = Math.max(
|
||||||
} else if (which.endsWith('Right')) {
|
newDims.height + dy,
|
||||||
newDims.width = Math.max(
|
stuffBeforeDrag.minDims.height,
|
||||||
newDims.width + dx,
|
)
|
||||||
stuffBeforeDrag.minDims.width,
|
} else if (which.startsWith('Top')) {
|
||||||
)
|
const bottom = newDims.top + newDims.height
|
||||||
}
|
const top = Math.min(
|
||||||
|
bottom - stuffBeforeDrag.minDims.height,
|
||||||
|
newDims.top + dy,
|
||||||
|
)
|
||||||
|
const height = bottom - top
|
||||||
|
|
||||||
const position = panelDimsToPanelPosition(newDims, {
|
newDims.height = height
|
||||||
width: window.innerWidth,
|
newDims.top = top
|
||||||
height: window.innerHeight,
|
}
|
||||||
})
|
if (which.endsWith('Left')) {
|
||||||
|
const right = newDims.left + newDims.width
|
||||||
|
const left = Math.min(
|
||||||
|
right - stuffBeforeDrag.minDims.width,
|
||||||
|
newDims.left + dx,
|
||||||
|
)
|
||||||
|
const width = right - left
|
||||||
|
|
||||||
tempTransaction?.discard()
|
newDims.width = width
|
||||||
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
newDims.left = left
|
||||||
stateEditors.studio.historic.panelPositions.setPanelPosition({
|
} else if (which.endsWith('Right')) {
|
||||||
position,
|
newDims.width = Math.max(
|
||||||
panelId: stuffBeforeDrag.panelId,
|
newDims.width + dx,
|
||||||
})
|
stuffBeforeDrag.minDims.width,
|
||||||
})
|
)
|
||||||
},
|
}
|
||||||
onDragEnd(dragHappened) {
|
|
||||||
if (unlock) {
|
const position = panelDimsToPanelPosition(newDims, {
|
||||||
const u = unlock
|
width: window.innerWidth,
|
||||||
unlock = undefined
|
height: window.innerHeight,
|
||||||
u()
|
})
|
||||||
|
|
||||||
|
tempTransaction?.discard()
|
||||||
|
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
||||||
|
stateEditors.studio.historic.panelPositions.setPanelPosition({
|
||||||
|
position,
|
||||||
|
panelId: stuffBeforeDrag.panelId,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onDragEnd(dragHappened) {
|
||||||
|
unlock()
|
||||||
|
if (dragHappened) {
|
||||||
|
tempTransaction?.commit()
|
||||||
|
} else {
|
||||||
|
tempTransaction?.discard()
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
setIsDragging(false)
|
|
||||||
if (dragHappened) {
|
|
||||||
tempTransaction?.commit()
|
|
||||||
} else {
|
|
||||||
tempTransaction?.discard()
|
|
||||||
}
|
|
||||||
tempTransaction = undefined
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [which])
|
}, [which])
|
||||||
|
|
||||||
useDrag(node, dragOpts)
|
const [isDragging] = useDrag(node, dragOpts)
|
||||||
const Comp = els[which]
|
const Comp = els[which]
|
||||||
|
|
||||||
const isOnCorner = which.length <= 6
|
const isOnCorner = which.length <= 6
|
||||||
|
|
|
@ -8,13 +8,8 @@ import {lighten} from 'polished'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {useMemo, useRef} from 'react'
|
import {useMemo, useRef} from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import type {
|
|
||||||
SequenceEditorPanelLayout,
|
|
||||||
DopeSheetSelection,
|
|
||||||
} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
|
||||||
import {DOT_SIZE_PX} from './KeyframeDot'
|
import {DOT_SIZE_PX} from './KeyframeDot'
|
||||||
import type KeyframeEditor from './KeyframeEditor'
|
import type KeyframeEditor from './KeyframeEditor'
|
||||||
import type Sequence from '@theatre/core/sequences/Sequence'
|
|
||||||
import usePopover from '@theatre/studio/uiComponents/Popover/usePopover'
|
import usePopover from '@theatre/studio/uiComponents/Popover/usePopover'
|
||||||
import BasicPopover from '@theatre/studio/uiComponents/Popover/BasicPopover'
|
import BasicPopover from '@theatre/studio/uiComponents/Popover/BasicPopover'
|
||||||
import CurveEditorPopover from './CurveEditorPopover/CurveEditorPopover'
|
import CurveEditorPopover from './CurveEditorPopover/CurveEditorPopover'
|
||||||
|
@ -156,84 +151,74 @@ function useDragKeyframe(node: HTMLDivElement | null, props: IProps) {
|
||||||
propsRef.current = props
|
propsRef.current = props
|
||||||
|
|
||||||
const gestureHandlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
|
const gestureHandlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
|
||||||
let toUnitSpace: SequenceEditorPanelLayout['scaledSpace']['toUnitSpace']
|
|
||||||
let tempTransaction: CommitOrDiscard | undefined
|
|
||||||
let propsAtStartOfDrag: IProps
|
|
||||||
let selectionDragHandlers:
|
|
||||||
| ReturnType<DopeSheetSelection['getDragHandlers']>
|
|
||||||
| undefined
|
|
||||||
let sequence: Sequence
|
|
||||||
return {
|
return {
|
||||||
debugName: 'useDragKeyframe',
|
debugName: 'useDragKeyframe',
|
||||||
lockCursorTo: 'ew-resize',
|
lockCursorTo: 'ew-resize',
|
||||||
onDragStart(event) {
|
onDragStart(event) {
|
||||||
const props = propsRef.current
|
const props = propsRef.current
|
||||||
|
let tempTransaction: CommitOrDiscard | undefined
|
||||||
|
|
||||||
if (props.selection) {
|
if (props.selection) {
|
||||||
const {selection, leaf} = props
|
const {selection, leaf} = props
|
||||||
const {sheetObject} = leaf
|
const {sheetObject} = leaf
|
||||||
selectionDragHandlers = selection.getDragHandlers({
|
return selection
|
||||||
...sheetObject.address,
|
.getDragHandlers({
|
||||||
pathToProp: leaf.pathToProp,
|
...sheetObject.address,
|
||||||
trackId: leaf.trackId,
|
pathToProp: leaf.pathToProp,
|
||||||
keyframeId: props.keyframe.id,
|
trackId: leaf.trackId,
|
||||||
domNode: node!,
|
keyframeId: props.keyframe.id,
|
||||||
positionAtStartOfDrag:
|
domNode: node!,
|
||||||
props.trackData.keyframes[props.index].position,
|
positionAtStartOfDrag:
|
||||||
})
|
props.trackData.keyframes[props.index].position,
|
||||||
selectionDragHandlers.onDragStart?.(event)
|
})
|
||||||
return
|
.onDragStart(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
propsAtStartOfDrag = props
|
const propsAtStartOfDrag = props
|
||||||
sequence = val(propsAtStartOfDrag.layoutP.sheet).getSequence()
|
const sequence = val(propsAtStartOfDrag.layoutP.sheet).getSequence()
|
||||||
|
|
||||||
toUnitSpace = val(propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace)
|
const toUnitSpace = val(
|
||||||
},
|
propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace,
|
||||||
onDrag(dx, dy, event) {
|
)
|
||||||
if (selectionDragHandlers) {
|
|
||||||
selectionDragHandlers.onDrag(dx, dy, event)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const delta = toUnitSpace(dx)
|
|
||||||
if (tempTransaction) {
|
|
||||||
tempTransaction.discard()
|
|
||||||
tempTransaction = undefined
|
|
||||||
}
|
|
||||||
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
|
||||||
stateEditors.coreByProject.historic.sheetsById.sequence.transformKeyframes(
|
|
||||||
{
|
|
||||||
...propsAtStartOfDrag.leaf.sheetObject.address,
|
|
||||||
trackId: propsAtStartOfDrag.leaf.trackId,
|
|
||||||
keyframeIds: [
|
|
||||||
propsAtStartOfDrag.keyframe.id,
|
|
||||||
propsAtStartOfDrag.trackData.keyframes[
|
|
||||||
propsAtStartOfDrag.index + 1
|
|
||||||
].id,
|
|
||||||
],
|
|
||||||
translate: delta,
|
|
||||||
scale: 1,
|
|
||||||
origin: 0,
|
|
||||||
snappingFunction: sequence.closestGridPosition,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onDragEnd(dragHappened) {
|
|
||||||
if (selectionDragHandlers) {
|
|
||||||
selectionDragHandlers.onDragEnd?.(dragHappened)
|
|
||||||
|
|
||||||
selectionDragHandlers = undefined
|
return {
|
||||||
|
onDrag(dx, dy, event) {
|
||||||
|
const delta = toUnitSpace(dx)
|
||||||
|
if (tempTransaction) {
|
||||||
|
tempTransaction.discard()
|
||||||
|
tempTransaction = undefined
|
||||||
|
}
|
||||||
|
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
||||||
|
stateEditors.coreByProject.historic.sheetsById.sequence.transformKeyframes(
|
||||||
|
{
|
||||||
|
...propsAtStartOfDrag.leaf.sheetObject.address,
|
||||||
|
trackId: propsAtStartOfDrag.leaf.trackId,
|
||||||
|
keyframeIds: [
|
||||||
|
propsAtStartOfDrag.keyframe.id,
|
||||||
|
propsAtStartOfDrag.trackData.keyframes[
|
||||||
|
propsAtStartOfDrag.index + 1
|
||||||
|
].id,
|
||||||
|
],
|
||||||
|
translate: delta,
|
||||||
|
scale: 1,
|
||||||
|
origin: 0,
|
||||||
|
snappingFunction: sequence.closestGridPosition,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onDragEnd(dragHappened) {
|
||||||
|
if (dragHappened) {
|
||||||
|
if (tempTransaction) {
|
||||||
|
tempTransaction.commit()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (tempTransaction) {
|
||||||
|
tempTransaction.discard()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if (dragHappened) {
|
|
||||||
if (tempTransaction) {
|
|
||||||
tempTransaction.commit()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (tempTransaction) {
|
|
||||||
tempTransaction.discard()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tempTransaction = undefined
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
|
@ -256,15 +256,17 @@ function useKeyframeDrag(
|
||||||
lockCursorTo: 'move',
|
lockCursorTo: 'move',
|
||||||
onDragStart() {
|
onDragStart() {
|
||||||
setFrozen(true)
|
setFrozen(true)
|
||||||
},
|
return {
|
||||||
onDrag(dx, dy) {
|
onDrag(dx, dy) {
|
||||||
if (!svgNode) return
|
if (!svgNode) return
|
||||||
|
|
||||||
props.onCurveChange(setHandles(dx, dy))
|
props.onCurveChange(setHandles(dx, dy))
|
||||||
},
|
},
|
||||||
onDragEnd(dragHappened) {
|
onDragEnd(dragHappened) {
|
||||||
setFrozen(false)
|
setFrozen(false)
|
||||||
props.onCancelCurveChange()
|
props.onCancelCurveChange()
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
[svgNode, props.trackData],
|
[svgNode, props.trackData],
|
||||||
|
|
|
@ -1,7 +1,3 @@
|
||||||
import type {
|
|
||||||
DopeSheetSelection,
|
|
||||||
SequenceEditorPanelLayout,
|
|
||||||
} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
|
||||||
import getStudio from '@theatre/studio/getStudio'
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
|
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
|
||||||
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
|
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
|
||||||
|
@ -148,14 +144,6 @@ function useDragKeyframe(
|
||||||
propsRef.current = props
|
propsRef.current = props
|
||||||
|
|
||||||
const useDragOpts = useMemo<UseDragOpts>(() => {
|
const useDragOpts = useMemo<UseDragOpts>(() => {
|
||||||
let toUnitSpace: SequenceEditorPanelLayout['scaledSpace']['toUnitSpace']
|
|
||||||
let tempTransaction: CommitOrDiscard | undefined
|
|
||||||
let propsAtStartOfDrag: IKeyframeDotProps
|
|
||||||
|
|
||||||
let selectionDragHandlers:
|
|
||||||
| ReturnType<DopeSheetSelection['getDragHandlers']>
|
|
||||||
| undefined
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
debugName: 'KeyframeDot/useDragKeyframe',
|
debugName: 'KeyframeDot/useDragKeyframe',
|
||||||
|
|
||||||
|
@ -164,65 +152,61 @@ function useDragKeyframe(
|
||||||
if (props.selection) {
|
if (props.selection) {
|
||||||
const {selection, leaf} = props
|
const {selection, leaf} = props
|
||||||
const {sheetObject} = leaf
|
const {sheetObject} = leaf
|
||||||
selectionDragHandlers = selection.getDragHandlers({
|
return selection
|
||||||
...sheetObject.address,
|
.getDragHandlers({
|
||||||
pathToProp: leaf.pathToProp,
|
...sheetObject.address,
|
||||||
trackId: leaf.trackId,
|
pathToProp: leaf.pathToProp,
|
||||||
keyframeId: props.keyframe.id,
|
trackId: leaf.trackId,
|
||||||
domNode: node!,
|
keyframeId: props.keyframe.id,
|
||||||
positionAtStartOfDrag:
|
domNode: node!,
|
||||||
props.trackData.keyframes[props.index].position,
|
positionAtStartOfDrag:
|
||||||
})
|
props.trackData.keyframes[props.index].position,
|
||||||
selectionDragHandlers.onDragStart?.(event)
|
})
|
||||||
return
|
.onDragStart(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
propsAtStartOfDrag = props
|
const propsAtStartOfDrag = props
|
||||||
toUnitSpace = val(propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace)
|
const toUnitSpace = val(
|
||||||
},
|
propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace,
|
||||||
onDrag(dx, dy, event) {
|
|
||||||
if (selectionDragHandlers) {
|
|
||||||
selectionDragHandlers.onDrag(dx, dy, event)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const original =
|
|
||||||
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index]
|
|
||||||
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
|
|
||||||
original.position + toUnitSpace(dx),
|
|
||||||
// sanitize to minimum of zero
|
|
||||||
0,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
tempTransaction?.discard()
|
let tempTransaction: CommitOrDiscard | undefined
|
||||||
tempTransaction = undefined
|
|
||||||
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
|
||||||
stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes(
|
|
||||||
{
|
|
||||||
...propsAtStartOfDrag.leaf.sheetObject.address,
|
|
||||||
trackId: propsAtStartOfDrag.leaf.trackId,
|
|
||||||
keyframes: [{...original, position: newPosition}],
|
|
||||||
snappingFunction: val(
|
|
||||||
propsAtStartOfDrag.layoutP.sheet,
|
|
||||||
).getSequence().closestGridPosition,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onDragEnd(dragHappened) {
|
|
||||||
if (selectionDragHandlers) {
|
|
||||||
selectionDragHandlers.onDragEnd?.(dragHappened)
|
|
||||||
|
|
||||||
selectionDragHandlers = undefined
|
return {
|
||||||
|
onDrag(dx, dy, event) {
|
||||||
|
const original =
|
||||||
|
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index]
|
||||||
|
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
|
||||||
|
original.position + toUnitSpace(dx),
|
||||||
|
// sanitize to minimum of zero
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
|
||||||
|
tempTransaction?.discard()
|
||||||
|
tempTransaction = undefined
|
||||||
|
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
||||||
|
stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes(
|
||||||
|
{
|
||||||
|
...propsAtStartOfDrag.leaf.sheetObject.address,
|
||||||
|
trackId: propsAtStartOfDrag.leaf.trackId,
|
||||||
|
keyframes: [{...original, position: newPosition}],
|
||||||
|
snappingFunction: val(
|
||||||
|
propsAtStartOfDrag.layoutP.sheet,
|
||||||
|
).getSequence().closestGridPosition,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onDragEnd(dragHappened) {
|
||||||
|
if (dragHappened) tempTransaction?.commit()
|
||||||
|
else tempTransaction?.discard()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if (dragHappened) tempTransaction?.commit()
|
|
||||||
else tempTransaction?.discard()
|
|
||||||
tempTransaction = undefined
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
|
@ -79,27 +79,30 @@ function useCaptureSelection(
|
||||||
}
|
}
|
||||||
|
|
||||||
val(layoutP.selectionAtom).setState({current: undefined})
|
val(layoutP.selectionAtom).setState({current: undefined})
|
||||||
},
|
|
||||||
onDrag(_dx, _dy, event) {
|
|
||||||
// const state = ref.current!
|
|
||||||
const rect = containerNode!.getBoundingClientRect()
|
|
||||||
|
|
||||||
const posInScaledSpace = event.clientX - rect.left
|
return {
|
||||||
|
onDrag(_dx, _dy, event) {
|
||||||
|
// const state = ref.current!
|
||||||
|
const rect = containerNode!.getBoundingClientRect()
|
||||||
|
|
||||||
const posInUnitSpace = val(layoutP.scaledSpace.toUnitSpace)(
|
const posInScaledSpace = event.clientX - rect.left
|
||||||
posInScaledSpace,
|
|
||||||
)
|
|
||||||
|
|
||||||
ref.current = {
|
const posInUnitSpace = val(layoutP.scaledSpace.toUnitSpace)(
|
||||||
positions: [ref.current!.positions[0], posInUnitSpace],
|
posInScaledSpace,
|
||||||
ys: [ref.current!.ys[0], event.clientY - rect.top],
|
)
|
||||||
|
|
||||||
|
ref.current = {
|
||||||
|
positions: [ref.current!.positions[0], posInUnitSpace],
|
||||||
|
ys: [ref.current!.ys[0], event.clientY - rect.top],
|
||||||
|
}
|
||||||
|
|
||||||
|
const selection = utils.boundsToSelection(layoutP, ref.current)
|
||||||
|
val(layoutP.selectionAtom).setState({current: selection})
|
||||||
|
},
|
||||||
|
onDragEnd(_dragHappened) {
|
||||||
|
ref.current = null
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const selection = utils.boundsToSelection(layoutP, ref.current)
|
|
||||||
val(layoutP.selectionAtom).setState({current: selection})
|
|
||||||
},
|
|
||||||
onDragEnd(_dragHappened) {
|
|
||||||
ref.current = null
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [layoutP, containerNode, ref]),
|
}, [layoutP, containerNode, ref]),
|
||||||
|
@ -183,59 +186,65 @@ namespace utils {
|
||||||
type: 'DopeSheetSelection',
|
type: 'DopeSheetSelection',
|
||||||
byObjectKey: {},
|
byObjectKey: {},
|
||||||
getDragHandlers(origin) {
|
getDragHandlers(origin) {
|
||||||
let tempTransaction: CommitOrDiscard | undefined
|
|
||||||
|
|
||||||
let toUnitSpace: SequenceEditorPanelLayout['scaledSpace']['toUnitSpace']
|
|
||||||
return {
|
return {
|
||||||
debugName: 'DopeSheetSelectionView/boundsToSelection',
|
debugName: 'DopeSheetSelectionView/boundsToSelection',
|
||||||
onDragStart() {
|
onDragStart() {
|
||||||
toUnitSpace = val(layoutP.scaledSpace.toUnitSpace)
|
let tempTransaction: CommitOrDiscard | undefined
|
||||||
},
|
|
||||||
onDrag(dx, _, event) {
|
|
||||||
if (tempTransaction) {
|
|
||||||
tempTransaction.discard()
|
|
||||||
tempTransaction = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, {
|
const toUnitSpace = val(layoutP.scaledSpace.toUnitSpace)
|
||||||
ignore: origin.domNode,
|
|
||||||
})
|
|
||||||
|
|
||||||
let delta: number
|
return {
|
||||||
if (snapPos != null) {
|
onDrag(dx, _, event) {
|
||||||
delta = snapPos - origin.positionAtStartOfDrag
|
if (tempTransaction) {
|
||||||
} else {
|
tempTransaction.discard()
|
||||||
delta = toUnitSpace(dx)
|
tempTransaction = undefined
|
||||||
}
|
|
||||||
|
|
||||||
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
|
||||||
const transformKeyframes =
|
|
||||||
stateEditors.coreByProject.historic.sheetsById.sequence
|
|
||||||
.transformKeyframes
|
|
||||||
|
|
||||||
for (const objectKey of Object.keys(selection.byObjectKey)) {
|
|
||||||
const {byTrackId} = selection.byObjectKey[objectKey]!
|
|
||||||
for (const trackId of Object.keys(byTrackId)) {
|
|
||||||
const {byKeyframeId} = byTrackId[trackId]!
|
|
||||||
transformKeyframes({
|
|
||||||
trackId,
|
|
||||||
keyframeIds: Object.keys(byKeyframeId),
|
|
||||||
translate: delta,
|
|
||||||
scale: 1,
|
|
||||||
origin: 0,
|
|
||||||
snappingFunction: sheet.getSequence().closestGridPosition,
|
|
||||||
objectKey,
|
|
||||||
projectId: origin.projectId,
|
|
||||||
sheetId: origin.sheetId,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, {
|
||||||
},
|
ignore: origin.domNode,
|
||||||
onDragEnd(dragHappened) {
|
})
|
||||||
if (dragHappened) tempTransaction?.commit()
|
|
||||||
else tempTransaction?.discard()
|
let delta: number
|
||||||
tempTransaction = undefined
|
if (snapPos != null) {
|
||||||
|
delta = snapPos - origin.positionAtStartOfDrag
|
||||||
|
} else {
|
||||||
|
delta = toUnitSpace(dx)
|
||||||
|
}
|
||||||
|
|
||||||
|
tempTransaction = getStudio()!.tempTransaction(
|
||||||
|
({stateEditors}) => {
|
||||||
|
const transformKeyframes =
|
||||||
|
stateEditors.coreByProject.historic.sheetsById.sequence
|
||||||
|
.transformKeyframes
|
||||||
|
|
||||||
|
for (const objectKey of Object.keys(
|
||||||
|
selection.byObjectKey,
|
||||||
|
)) {
|
||||||
|
const {byTrackId} = selection.byObjectKey[objectKey]!
|
||||||
|
for (const trackId of Object.keys(byTrackId)) {
|
||||||
|
const {byKeyframeId} = byTrackId[trackId]!
|
||||||
|
transformKeyframes({
|
||||||
|
trackId,
|
||||||
|
keyframeIds: Object.keys(byKeyframeId),
|
||||||
|
translate: delta,
|
||||||
|
scale: 1,
|
||||||
|
origin: 0,
|
||||||
|
snappingFunction:
|
||||||
|
sheet.getSequence().closestGridPosition,
|
||||||
|
objectKey,
|
||||||
|
projectId: origin.projectId,
|
||||||
|
sheetId: origin.sheetId,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onDragEnd(dragHappened) {
|
||||||
|
if (dragHappened) tempTransaction?.commit()
|
||||||
|
else tempTransaction?.discard()
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import type Sequence from '@theatre/core/sequences/Sequence'
|
|
||||||
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||||
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
||||||
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
||||||
|
@ -50,7 +49,7 @@ const HorizontallyScrollableArea: React.FC<{
|
||||||
)
|
)
|
||||||
|
|
||||||
useHandlePanAndZoom(layoutP, containerNode)
|
useHandlePanAndZoom(layoutP, containerNode)
|
||||||
useDragHandlers(layoutP, containerNode)
|
useDragPlayheadHandlers(layoutP, containerNode)
|
||||||
useUpdateScrollFromClippedSpaceRange(layoutP, containerNode)
|
useUpdateScrollFromClippedSpaceRange(layoutP, containerNode)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -70,31 +69,13 @@ const HorizontallyScrollableArea: React.FC<{
|
||||||
|
|
||||||
export default HorizontallyScrollableArea
|
export default HorizontallyScrollableArea
|
||||||
|
|
||||||
function useDragHandlers(
|
function useDragPlayheadHandlers(
|
||||||
layoutP: Pointer<SequenceEditorPanelLayout>,
|
layoutP: Pointer<SequenceEditorPanelLayout>,
|
||||||
containerEl: HTMLDivElement | null,
|
containerEl: HTMLDivElement | null,
|
||||||
) {
|
) {
|
||||||
const handlers = useMemo((): Parameters<typeof useDrag>[1] => {
|
const handlers = useMemo((): Parameters<typeof useDrag>[1] => {
|
||||||
let posBeforeSeek = 0
|
|
||||||
let sequence: Sequence
|
|
||||||
let scaledSpaceToUnitSpace: typeof layoutP.scaledSpace.toUnitSpace.$$__pointer_type
|
|
||||||
const setIsSeeking = val(layoutP.seeker.setIsSeeking)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
debugName: 'HorizontallyScrollableArea',
|
debugName: 'HorizontallyScrollableArea',
|
||||||
onDrag(dx: number, _, event) {
|
|
||||||
const deltaPos = scaledSpaceToUnitSpace(dx)
|
|
||||||
const unsnappedPos = clamp(posBeforeSeek + deltaPos, 0, sequence.length)
|
|
||||||
|
|
||||||
let newPosition = unsnappedPos
|
|
||||||
|
|
||||||
const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, {})
|
|
||||||
if (snapPos != null) {
|
|
||||||
newPosition = snapPos
|
|
||||||
}
|
|
||||||
|
|
||||||
sequence.position = newPosition
|
|
||||||
},
|
|
||||||
onDragStart(event) {
|
onDragStart(event) {
|
||||||
if (event.target instanceof HTMLInputElement) {
|
if (event.target instanceof HTMLInputElement) {
|
||||||
// editing some value
|
// editing some value
|
||||||
|
@ -124,16 +105,38 @@ function useDragHandlers(
|
||||||
Infinity,
|
Infinity,
|
||||||
)
|
)
|
||||||
|
|
||||||
sequence = val(layoutP.sheet).getSequence()
|
const setIsSeeking = val(layoutP.seeker.setIsSeeking)
|
||||||
|
|
||||||
|
const sequence = val(layoutP.sheet).getSequence()
|
||||||
|
|
||||||
sequence.position = initialPositionInUnitSpace
|
sequence.position = initialPositionInUnitSpace
|
||||||
|
|
||||||
posBeforeSeek = initialPositionInUnitSpace
|
const posBeforeSeek = initialPositionInUnitSpace
|
||||||
scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace)
|
const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace)
|
||||||
setIsSeeking(true)
|
setIsSeeking(true)
|
||||||
},
|
|
||||||
onDragEnd() {
|
return {
|
||||||
setIsSeeking(false)
|
onDrag(dx: number, _, event) {
|
||||||
|
const deltaPos = scaledSpaceToUnitSpace(dx)
|
||||||
|
const unsnappedPos = clamp(
|
||||||
|
posBeforeSeek + deltaPos,
|
||||||
|
0,
|
||||||
|
sequence.length,
|
||||||
|
)
|
||||||
|
|
||||||
|
let newPosition = unsnappedPos
|
||||||
|
|
||||||
|
const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, {})
|
||||||
|
if (snapPos != null) {
|
||||||
|
newPosition = snapPos
|
||||||
|
}
|
||||||
|
|
||||||
|
sequence.position = newPosition
|
||||||
|
},
|
||||||
|
onDragEnd() {
|
||||||
|
setIsSeeking(false)
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [layoutP, containerEl])
|
}, [layoutP, containerEl])
|
||||||
|
@ -233,6 +236,39 @@ function useHandlePanAndZoom(
|
||||||
node.removeEventListener('wheel', receiveWheelEvent, listenerOptions)
|
node.removeEventListener('wheel', receiveWheelEvent, listenerOptions)
|
||||||
}
|
}
|
||||||
}, [node, layoutP])
|
}, [node, layoutP])
|
||||||
|
|
||||||
|
useDrag(
|
||||||
|
node,
|
||||||
|
useMemo<Parameters<typeof useDrag>[1]>(() => {
|
||||||
|
return {
|
||||||
|
onDragStart(e) {
|
||||||
|
const oldRange = val(layoutP.clippedSpace.range)
|
||||||
|
const setRange = val(layoutP.clippedSpace.setRange)
|
||||||
|
const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace)
|
||||||
|
e.preventDefault()
|
||||||
|
e.stopPropagation()
|
||||||
|
|
||||||
|
return {
|
||||||
|
onDrag(dx, dy, _, __, deltaYFromLastEvent) {
|
||||||
|
receiveVerticalWheelEvent({deltaY: -deltaYFromLastEvent})
|
||||||
|
const delta = -scaledSpaceToUnitSpace(dx)
|
||||||
|
|
||||||
|
const newRange = mapValues(
|
||||||
|
oldRange,
|
||||||
|
(originalPos) => originalPos + delta,
|
||||||
|
)
|
||||||
|
|
||||||
|
setRange(newRange)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
debugName: 'HorizontallyScrollableArea Middle Button Drag',
|
||||||
|
buttons: [1],
|
||||||
|
lockCursorTo: 'grab',
|
||||||
|
}
|
||||||
|
}, [layoutP]),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalize(value: number, [min, max]: [min: number, max: number]) {
|
function normalize(value: number, [min, max]: [min: number, max: number]) {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {usePrism} from '@theatre/react'
|
import {usePrism} from '@theatre/react'
|
||||||
import type {Pointer} from '@theatre/dataverse'
|
import type {Pointer} from '@theatre/dataverse'
|
||||||
import {val} from '@theatre/dataverse'
|
import {val} from '@theatre/dataverse'
|
||||||
import React, {useMemo, useRef, useState} from 'react'
|
import React, {useMemo, useRef} from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||||
import {zIndexes} from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel'
|
import {zIndexes} from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel'
|
||||||
|
@ -10,7 +10,6 @@ import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
||||||
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
|
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
|
||||||
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
||||||
import getStudio from '@theatre/studio/getStudio'
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
import type Sheet from '@theatre/core/sheets/Sheet'
|
|
||||||
import usePopover from '@theatre/studio/uiComponents/Popover/usePopover'
|
import usePopover from '@theatre/studio/uiComponents/Popover/usePopover'
|
||||||
import {
|
import {
|
||||||
includeLockFrameStampAttrs,
|
includeLockFrameStampAttrs,
|
||||||
|
@ -219,58 +218,56 @@ function useDragBulge(
|
||||||
): [isDragging: boolean] {
|
): [isDragging: boolean] {
|
||||||
const propsRef = useRef(props)
|
const propsRef = useRef(props)
|
||||||
propsRef.current = props
|
propsRef.current = props
|
||||||
const [isDragging, setIsDragging] = useState(false)
|
|
||||||
|
|
||||||
useLockFrameStampPosition(isDragging, -1)
|
|
||||||
|
|
||||||
const gestureHandlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
|
const gestureHandlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
|
||||||
let toUnitSpace: SequenceEditorPanelLayout['scaledSpace']['toUnitSpace']
|
|
||||||
let tempTransaction: CommitOrDiscard | undefined
|
|
||||||
let propsAtStartOfDrag: IProps
|
|
||||||
let sheet: Sheet
|
|
||||||
let initialLength: number
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
debugName: 'LengthIndicator/useDragBulge',
|
debugName: 'LengthIndicator/useDragBulge',
|
||||||
lockCursorTo: 'ew-resize',
|
lockCursorTo: 'ew-resize',
|
||||||
onDragStart(event) {
|
onDragStart(event) {
|
||||||
setIsDragging(true)
|
let tempTransaction: CommitOrDiscard | undefined
|
||||||
propsAtStartOfDrag = propsRef.current
|
|
||||||
sheet = val(propsRef.current.layoutP.sheet)
|
|
||||||
initialLength = sheet.getSequence().length
|
|
||||||
|
|
||||||
toUnitSpace = val(propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace)
|
const propsAtStartOfDrag = propsRef.current
|
||||||
},
|
const sheet = val(propsRef.current.layoutP.sheet)
|
||||||
onDrag(dx, dy, event) {
|
const initialLength = sheet.getSequence().length
|
||||||
const delta = toUnitSpace(dx)
|
|
||||||
if (tempTransaction) {
|
const toUnitSpace = val(
|
||||||
tempTransaction.discard()
|
propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace,
|
||||||
tempTransaction = undefined
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
onDrag(dx, dy, event) {
|
||||||
|
const delta = toUnitSpace(dx)
|
||||||
|
if (tempTransaction) {
|
||||||
|
tempTransaction.discard()
|
||||||
|
tempTransaction = undefined
|
||||||
|
}
|
||||||
|
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
||||||
|
stateEditors.coreByProject.historic.sheetsById.sequence.setLength(
|
||||||
|
{
|
||||||
|
...sheet.address,
|
||||||
|
length: initialLength + delta,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onDragEnd(dragHappened) {
|
||||||
|
if (dragHappened) {
|
||||||
|
if (tempTransaction) {
|
||||||
|
tempTransaction.commit()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (tempTransaction) {
|
||||||
|
tempTransaction.discard()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
|
||||||
stateEditors.coreByProject.historic.sheetsById.sequence.setLength({
|
|
||||||
...sheet.address,
|
|
||||||
length: initialLength + delta,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onDragEnd(dragHappened) {
|
|
||||||
setIsDragging(false)
|
|
||||||
if (dragHappened) {
|
|
||||||
if (tempTransaction) {
|
|
||||||
tempTransaction.commit()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (tempTransaction) {
|
|
||||||
tempTransaction.discard()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tempTransaction = undefined
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useDrag(node, gestureHandlers)
|
const [isDragging] = useDrag(node, gestureHandlers)
|
||||||
|
useLockFrameStampPosition(isDragging, -1)
|
||||||
|
|
||||||
return [isDragging]
|
return [isDragging]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
|
||||||
import getStudio from '@theatre/studio/getStudio'
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
|
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
|
||||||
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
|
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
|
||||||
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
||||||
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
||||||
import type {VoidFn} from '@theatre/shared/utils/types'
|
|
||||||
import {val} from '@theatre/dataverse'
|
import {val} from '@theatre/dataverse'
|
||||||
import {clamp} from 'lodash-es'
|
import {clamp} from 'lodash-es'
|
||||||
import React, {useMemo, useRef} from 'react'
|
import React, {useMemo, useRef} from 'react'
|
||||||
|
@ -125,115 +123,130 @@ function useOurDrags(node: SVGCircleElement | null, props: IProps): void {
|
||||||
propsRef.current = props
|
propsRef.current = props
|
||||||
|
|
||||||
const handlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
|
const handlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
|
||||||
let scaledToUnitSpace: SequenceEditorPanelLayout['scaledSpace']['toUnitSpace']
|
|
||||||
let verticalToExtremumSpace: SequenceEditorPanelLayout['graphEditorVerticalSpace']['toExtremumSpace']
|
|
||||||
let propsAtStartOfDrag: IProps
|
|
||||||
let tempTransaction: CommitOrDiscard | undefined
|
|
||||||
let unlockExtremums: VoidFn | undefined
|
|
||||||
return {
|
return {
|
||||||
debugName: 'CurveHandler/useOurDrags',
|
debugName: 'CurveHandler/useOurDrags',
|
||||||
lockCursorTo: 'move',
|
lockCursorTo: 'move',
|
||||||
onDragStart() {
|
onDragStart() {
|
||||||
propsAtStartOfDrag = propsRef.current
|
let tempTransaction: CommitOrDiscard | undefined
|
||||||
|
|
||||||
scaledToUnitSpace = val(
|
const propsAtStartOfDrag = propsRef.current
|
||||||
|
|
||||||
|
const scaledToUnitSpace = val(
|
||||||
propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace,
|
propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace,
|
||||||
)
|
)
|
||||||
verticalToExtremumSpace = val(
|
const verticalToExtremumSpace = val(
|
||||||
propsAtStartOfDrag.layoutP.graphEditorVerticalSpace.toExtremumSpace,
|
propsAtStartOfDrag.layoutP.graphEditorVerticalSpace.toExtremumSpace,
|
||||||
)
|
)
|
||||||
|
|
||||||
unlockExtremums = propsAtStartOfDrag.extremumSpace.lock()
|
const unlockExtremums = propsAtStartOfDrag.extremumSpace.lock()
|
||||||
},
|
|
||||||
onDrag(dxInScaledSpace, dy) {
|
return {
|
||||||
if (tempTransaction) {
|
onDrag(dxInScaledSpace, dy) {
|
||||||
tempTransaction.discard()
|
if (tempTransaction) {
|
||||||
tempTransaction = undefined
|
tempTransaction.discard()
|
||||||
|
tempTransaction = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const {index, trackData} = propsAtStartOfDrag
|
||||||
|
const cur = trackData.keyframes[index]
|
||||||
|
const next = trackData.keyframes[index + 1]
|
||||||
|
|
||||||
|
const dPosInUnitSpace = scaledToUnitSpace(dxInScaledSpace)
|
||||||
|
let dPosInKeyframeDiffSpace =
|
||||||
|
dPosInUnitSpace / (next.position - cur.position)
|
||||||
|
|
||||||
|
const dyInVerticalSpace = -dy
|
||||||
|
const dYInExtremumSpace = verticalToExtremumSpace(dyInVerticalSpace)
|
||||||
|
|
||||||
|
const dYInValueSpace =
|
||||||
|
propsAtStartOfDrag.extremumSpace.deltaToValueSpace(
|
||||||
|
dYInExtremumSpace,
|
||||||
|
)
|
||||||
|
|
||||||
|
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') {
|
||||||
|
const handleX = clamp(
|
||||||
|
cur.handles[2] + dPosInKeyframeDiffSpace,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
const handleY = cur.handles[3] + dyInKeyframeDiffSpace
|
||||||
|
|
||||||
|
tempTransaction = getStudio()!.tempTransaction(
|
||||||
|
({stateEditors}) => {
|
||||||
|
stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes(
|
||||||
|
{
|
||||||
|
...propsAtStartOfDrag.sheetObject.address,
|
||||||
|
snappingFunction: val(
|
||||||
|
propsAtStartOfDrag.layoutP.sheet,
|
||||||
|
).getSequence().closestGridPosition,
|
||||||
|
trackId: propsAtStartOfDrag.trackId,
|
||||||
|
keyframes: [
|
||||||
|
{
|
||||||
|
...cur,
|
||||||
|
handles: [
|
||||||
|
cur.handles[0],
|
||||||
|
cur.handles[1],
|
||||||
|
handleX,
|
||||||
|
handleY,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
const handleX = clamp(
|
||||||
|
next.handles[0] + dPosInKeyframeDiffSpace,
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
const handleY = next.handles[1] + dyInKeyframeDiffSpace
|
||||||
|
|
||||||
|
tempTransaction = getStudio()!.tempTransaction(
|
||||||
|
({stateEditors}) => {
|
||||||
|
stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes(
|
||||||
|
{
|
||||||
|
...propsAtStartOfDrag.sheetObject.address,
|
||||||
|
trackId: propsAtStartOfDrag.trackId,
|
||||||
|
snappingFunction: val(
|
||||||
|
propsAtStartOfDrag.layoutP.sheet,
|
||||||
|
).getSequence().closestGridPosition,
|
||||||
|
keyframes: [
|
||||||
|
{
|
||||||
|
...next,
|
||||||
|
handles: [
|
||||||
|
handleX,
|
||||||
|
handleY,
|
||||||
|
next.handles[2],
|
||||||
|
next.handles[3],
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDragEnd(dragHappened) {
|
||||||
|
unlockExtremums()
|
||||||
|
if (dragHappened) {
|
||||||
|
if (tempTransaction) {
|
||||||
|
tempTransaction.commit()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (tempTransaction) {
|
||||||
|
tempTransaction.discard()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
const {index, trackData} = propsAtStartOfDrag
|
|
||||||
const cur = trackData.keyframes[index]
|
|
||||||
const next = trackData.keyframes[index + 1]
|
|
||||||
|
|
||||||
const dPosInUnitSpace = scaledToUnitSpace(dxInScaledSpace)
|
|
||||||
let dPosInKeyframeDiffSpace =
|
|
||||||
dPosInUnitSpace / (next.position - cur.position)
|
|
||||||
|
|
||||||
const dyInVerticalSpace = -dy
|
|
||||||
const dYInExtremumSpace = verticalToExtremumSpace(dyInVerticalSpace)
|
|
||||||
|
|
||||||
const dYInValueSpace =
|
|
||||||
propsAtStartOfDrag.extremumSpace.deltaToValueSpace(dYInExtremumSpace)
|
|
||||||
|
|
||||||
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') {
|
|
||||||
const handleX = clamp(cur.handles[2] + dPosInKeyframeDiffSpace, 0, 1)
|
|
||||||
const handleY = cur.handles[3] + dyInKeyframeDiffSpace
|
|
||||||
|
|
||||||
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
|
||||||
stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes(
|
|
||||||
{
|
|
||||||
...propsAtStartOfDrag.sheetObject.address,
|
|
||||||
snappingFunction: val(
|
|
||||||
propsAtStartOfDrag.layoutP.sheet,
|
|
||||||
).getSequence().closestGridPosition,
|
|
||||||
trackId: propsAtStartOfDrag.trackId,
|
|
||||||
keyframes: [
|
|
||||||
{
|
|
||||||
...cur,
|
|
||||||
handles: [cur.handles[0], cur.handles[1], handleX, handleY],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
const handleX = clamp(next.handles[0] + dPosInKeyframeDiffSpace, 0, 1)
|
|
||||||
const handleY = next.handles[1] + dyInKeyframeDiffSpace
|
|
||||||
|
|
||||||
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
|
||||||
stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes(
|
|
||||||
{
|
|
||||||
...propsAtStartOfDrag.sheetObject.address,
|
|
||||||
trackId: propsAtStartOfDrag.trackId,
|
|
||||||
snappingFunction: val(
|
|
||||||
propsAtStartOfDrag.layoutP.sheet,
|
|
||||||
).getSequence().closestGridPosition,
|
|
||||||
keyframes: [
|
|
||||||
{
|
|
||||||
...next,
|
|
||||||
handles: [
|
|
||||||
handleX,
|
|
||||||
handleY,
|
|
||||||
next.handles[2],
|
|
||||||
next.handles[3],
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDragEnd(dragHappened) {
|
|
||||||
if (unlockExtremums) {
|
|
||||||
const unlock = unlockExtremums
|
|
||||||
unlockExtremums = undefined
|
|
||||||
unlock()
|
|
||||||
}
|
|
||||||
if (dragHappened) {
|
|
||||||
if (tempTransaction) {
|
|
||||||
tempTransaction.commit()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (tempTransaction) {
|
|
||||||
tempTransaction.discard()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tempTransaction = undefined
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
|
||||||
import getStudio from '@theatre/studio/getStudio'
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
|
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
|
||||||
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
|
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
|
||||||
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
||||||
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
||||||
import type {VoidFn} from '@theatre/shared/utils/types'
|
|
||||||
import {val} from '@theatre/dataverse'
|
import {val} from '@theatre/dataverse'
|
||||||
import React, {useMemo, useRef, useState} from 'react'
|
import React, {useMemo, useRef, useState} from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
@ -107,68 +105,62 @@ function useDragKeyframe(
|
||||||
propsRef.current = _props
|
propsRef.current = _props
|
||||||
|
|
||||||
const gestureHandlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
|
const gestureHandlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
|
||||||
let toUnitSpace: SequenceEditorPanelLayout['scaledSpace']['toUnitSpace']
|
|
||||||
|
|
||||||
let propsAtStartOfDrag: IProps
|
|
||||||
let tempTransaction: CommitOrDiscard | undefined
|
|
||||||
let unlockExtremums: VoidFn | undefined
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
debugName: 'GraphEditorDotNonScalar/useDragKeyframe',
|
debugName: 'GraphEditorDotNonScalar/useDragKeyframe',
|
||||||
lockCursorTo: 'ew-resize',
|
lockCursorTo: 'ew-resize',
|
||||||
onDragStart(event) {
|
onDragStart(event) {
|
||||||
setIsDragging(true)
|
setIsDragging(true)
|
||||||
|
const propsAtStartOfDrag = propsRef.current
|
||||||
|
|
||||||
propsAtStartOfDrag = propsRef.current
|
const toUnitSpace = val(
|
||||||
|
propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace,
|
||||||
|
)
|
||||||
|
|
||||||
toUnitSpace = val(propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace)
|
const unlockExtremums = propsAtStartOfDrag.extremumSpace.lock()
|
||||||
|
let tempTransaction: CommitOrDiscard | undefined
|
||||||
|
|
||||||
unlockExtremums = propsAtStartOfDrag.extremumSpace.lock()
|
return {
|
||||||
},
|
onDrag(dx, dy) {
|
||||||
onDrag(dx, dy) {
|
const original =
|
||||||
const original =
|
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index]
|
||||||
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index]
|
|
||||||
|
|
||||||
const deltaPos = toUnitSpace(dx)
|
const deltaPos = toUnitSpace(dx)
|
||||||
|
|
||||||
const updatedKeyframes: Keyframe[] = []
|
const updatedKeyframes: Keyframe[] = []
|
||||||
|
|
||||||
const cur: Keyframe = {
|
const cur: Keyframe = {
|
||||||
...original,
|
...original,
|
||||||
position: original.position + deltaPos,
|
position: original.position + deltaPos,
|
||||||
value: original.value,
|
value: original.value,
|
||||||
handles: [...original.handles],
|
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)
|
||||||
|
unlockExtremums()
|
||||||
|
if (dragHappened) {
|
||||||
|
tempTransaction?.commit()
|
||||||
|
} else {
|
||||||
|
tempTransaction?.discard()
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
|
@ -1,10 +1,8 @@
|
||||||
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
|
||||||
import getStudio from '@theatre/studio/getStudio'
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
|
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
|
||||||
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
|
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
|
||||||
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
||||||
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
||||||
import type {VoidFn} from '@theatre/shared/utils/types'
|
|
||||||
import {val} from '@theatre/dataverse'
|
import {val} from '@theatre/dataverse'
|
||||||
import React, {useMemo, useRef, useState} from 'react'
|
import React, {useMemo, useRef, useState} from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
@ -108,121 +106,122 @@ function useDragKeyframe(
|
||||||
propsRef.current = _props
|
propsRef.current = _props
|
||||||
|
|
||||||
const gestureHandlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
|
const gestureHandlers = useMemo<Parameters<typeof useDrag>[1]>(() => {
|
||||||
let toUnitSpace: SequenceEditorPanelLayout['scaledSpace']['toUnitSpace']
|
|
||||||
|
|
||||||
let propsAtStartOfDrag: IProps
|
|
||||||
let tempTransaction: CommitOrDiscard | undefined
|
|
||||||
let verticalToExtremumSpace: SequenceEditorPanelLayout['graphEditorVerticalSpace']['toExtremumSpace']
|
|
||||||
let unlockExtremums: VoidFn | undefined
|
|
||||||
let keepSpeeds = false
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
debugName: 'GraphEditorDotScalar/useDragKeyframe',
|
debugName: 'GraphEditorDotScalar/useDragKeyframe',
|
||||||
lockCursorTo: 'move',
|
lockCursorTo: 'move',
|
||||||
onDragStart(event) {
|
onDragStart(event) {
|
||||||
setIsDragging(true)
|
setIsDragging(true)
|
||||||
keepSpeeds = !!event.altKey
|
const keepSpeeds = !!event.altKey
|
||||||
|
|
||||||
propsAtStartOfDrag = propsRef.current
|
const propsAtStartOfDrag = propsRef.current
|
||||||
|
|
||||||
toUnitSpace = val(propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace)
|
const toUnitSpace = val(
|
||||||
verticalToExtremumSpace = val(
|
propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace,
|
||||||
|
)
|
||||||
|
const verticalToExtremumSpace = val(
|
||||||
propsAtStartOfDrag.layoutP.graphEditorVerticalSpace.toExtremumSpace,
|
propsAtStartOfDrag.layoutP.graphEditorVerticalSpace.toExtremumSpace,
|
||||||
)
|
)
|
||||||
unlockExtremums = propsAtStartOfDrag.extremumSpace.lock()
|
const unlockExtremums = propsAtStartOfDrag.extremumSpace.lock()
|
||||||
},
|
let tempTransaction: CommitOrDiscard | undefined
|
||||||
onDrag(dx, dy) {
|
|
||||||
const original =
|
|
||||||
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index]
|
|
||||||
|
|
||||||
const deltaPos = toUnitSpace(dx)
|
return {
|
||||||
const dyInVerticalSpace = -dy
|
onDrag(dx, dy) {
|
||||||
const dYInExtremumSpace = verticalToExtremumSpace(dyInVerticalSpace)
|
const original =
|
||||||
|
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index]
|
||||||
|
|
||||||
const dYInValueSpace =
|
const deltaPos = toUnitSpace(dx)
|
||||||
propsAtStartOfDrag.extremumSpace.deltaToValueSpace(dYInExtremumSpace)
|
const dyInVerticalSpace = -dy
|
||||||
|
const dYInExtremumSpace = verticalToExtremumSpace(dyInVerticalSpace)
|
||||||
|
|
||||||
const updatedKeyframes: Keyframe[] = []
|
const dYInValueSpace =
|
||||||
|
propsAtStartOfDrag.extremumSpace.deltaToValueSpace(
|
||||||
|
dYInExtremumSpace,
|
||||||
|
)
|
||||||
|
|
||||||
const cur: Keyframe = {
|
const updatedKeyframes: Keyframe[] = []
|
||||||
...original,
|
|
||||||
position: original.position + deltaPos,
|
|
||||||
value: (original.value as number) + dYInValueSpace,
|
|
||||||
handles: [...original.handles],
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedKeyframes.push(cur)
|
const cur: Keyframe = {
|
||||||
|
...original,
|
||||||
if (keepSpeeds) {
|
position: original.position + deltaPos,
|
||||||
const prev =
|
value: (original.value as number) + dYInValueSpace,
|
||||||
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index - 1]
|
handles: [...original.handles],
|
||||||
|
|
||||||
if (
|
|
||||||
prev &&
|
|
||||||
Math.abs((original.value as number) - (prev.value as number)) > 0
|
|
||||||
) {
|
|
||||||
const newPrev: Keyframe = {
|
|
||||||
...prev,
|
|
||||||
handles: [...prev.handles],
|
|
||||||
}
|
}
|
||||||
updatedKeyframes.push(newPrev)
|
|
||||||
newPrev.handles[3] = preserveRightHandle(
|
|
||||||
prev.handles[3],
|
|
||||||
prev.value as number,
|
|
||||||
prev.value as number,
|
|
||||||
original.value as number,
|
|
||||||
cur.value as number,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
const next =
|
|
||||||
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index + 1]
|
|
||||||
|
|
||||||
if (
|
updatedKeyframes.push(cur)
|
||||||
next &&
|
|
||||||
Math.abs((original.value as number) - (next.value as number)) > 0
|
if (keepSpeeds) {
|
||||||
) {
|
const prev =
|
||||||
const newNext: Keyframe = {
|
propsAtStartOfDrag.trackData.keyframes[
|
||||||
...next,
|
propsAtStartOfDrag.index - 1
|
||||||
handles: [...next.handles],
|
]
|
||||||
|
|
||||||
|
if (
|
||||||
|
prev &&
|
||||||
|
Math.abs((original.value as number) - (prev.value as number)) >
|
||||||
|
0
|
||||||
|
) {
|
||||||
|
const newPrev: Keyframe = {
|
||||||
|
...prev,
|
||||||
|
handles: [...prev.handles],
|
||||||
|
}
|
||||||
|
updatedKeyframes.push(newPrev)
|
||||||
|
newPrev.handles[3] = preserveRightHandle(
|
||||||
|
prev.handles[3],
|
||||||
|
prev.value as number,
|
||||||
|
prev.value as number,
|
||||||
|
original.value as number,
|
||||||
|
cur.value as number,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
const next =
|
||||||
|
propsAtStartOfDrag.trackData.keyframes[
|
||||||
|
propsAtStartOfDrag.index + 1
|
||||||
|
]
|
||||||
|
|
||||||
|
if (
|
||||||
|
next &&
|
||||||
|
Math.abs((original.value as number) - (next.value as number)) >
|
||||||
|
0
|
||||||
|
) {
|
||||||
|
const newNext: Keyframe = {
|
||||||
|
...next,
|
||||||
|
handles: [...next.handles],
|
||||||
|
}
|
||||||
|
updatedKeyframes.push(newNext)
|
||||||
|
newNext.handles[1] = preserveLeftHandle(
|
||||||
|
newNext.handles[1],
|
||||||
|
newNext.value as number,
|
||||||
|
newNext.value as number,
|
||||||
|
original.value as number,
|
||||||
|
cur.value as number,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updatedKeyframes.push(newNext)
|
|
||||||
newNext.handles[1] = preserveLeftHandle(
|
|
||||||
newNext.handles[1],
|
|
||||||
newNext.value as number,
|
|
||||||
newNext.value as number,
|
|
||||||
original.value as number,
|
|
||||||
cur.value as number,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tempTransaction?.discard()
|
tempTransaction?.discard()
|
||||||
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
||||||
stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes(
|
stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes(
|
||||||
{
|
{
|
||||||
...propsAtStartOfDrag.sheetObject.address,
|
...propsAtStartOfDrag.sheetObject.address,
|
||||||
trackId: propsAtStartOfDrag.trackId,
|
trackId: propsAtStartOfDrag.trackId,
|
||||||
keyframes: updatedKeyframes,
|
keyframes: updatedKeyframes,
|
||||||
snappingFunction: val(
|
snappingFunction: val(
|
||||||
propsAtStartOfDrag.layoutP.sheet,
|
propsAtStartOfDrag.layoutP.sheet,
|
||||||
).getSequence().closestGridPosition,
|
).getSequence().closestGridPosition,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onDragEnd(dragHappened) {
|
onDragEnd(dragHappened) {
|
||||||
setIsDragging(false)
|
setIsDragging(false)
|
||||||
if (unlockExtremums) {
|
unlockExtremums()
|
||||||
const unlock = unlockExtremums
|
if (dragHappened) {
|
||||||
unlockExtremums = undefined
|
tempTransaction?.commit()
|
||||||
unlock()
|
} else {
|
||||||
|
tempTransaction?.discard()
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if (dragHappened) {
|
|
||||||
tempTransaction?.commit()
|
|
||||||
} else {
|
|
||||||
tempTransaction?.discard()
|
|
||||||
}
|
|
||||||
tempTransaction = undefined
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import type {Pointer} from '@theatre/dataverse'
|
import type {Pointer} from '@theatre/dataverse'
|
||||||
import {prism, val} from '@theatre/dataverse'
|
import {prism, val} from '@theatre/dataverse'
|
||||||
import {usePrism, useVal} from '@theatre/react'
|
import {usePrism, useVal} from '@theatre/react'
|
||||||
import type {$IntentionalAny, IRange} from '@theatre/shared/utils/types'
|
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
||||||
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||||
import getStudio from '@theatre/studio/getStudio'
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||||
|
@ -179,81 +179,73 @@ const FocusRangeStrip: React.FC<{
|
||||||
const sheet = useVal(layoutP.sheet)
|
const sheet = useVal(layoutP.sheet)
|
||||||
|
|
||||||
const gestureHandlers = useMemo((): Parameters<typeof useDrag>[1] => {
|
const gestureHandlers = useMemo((): Parameters<typeof useDrag>[1] => {
|
||||||
let sequence = sheet.getSequence()
|
|
||||||
let startPosBeforeDrag: number,
|
|
||||||
endPosBeforeDrag: number,
|
|
||||||
tempTransaction: CommitOrDiscard | undefined
|
|
||||||
let dragHappened = false
|
|
||||||
let existingRange: {enabled: boolean; range: IRange<number>} | undefined
|
|
||||||
let target: HTMLDivElement | undefined
|
|
||||||
let newStartPosition: number, newEndPosition: number
|
let newStartPosition: number, newEndPosition: number
|
||||||
|
|
||||||
return {
|
return {
|
||||||
debugName: 'FocusRangeStrip',
|
debugName: 'FocusRangeStrip',
|
||||||
onDragStart(event) {
|
onDragStart(event) {
|
||||||
existingRange = existingRangeD.getValue()
|
let tempTransaction: CommitOrDiscard | undefined
|
||||||
|
let existingRange = existingRangeD.getValue()
|
||||||
|
if (!existingRange) return false
|
||||||
|
|
||||||
if (existingRange) {
|
const startPosBeforeDrag = existingRange.range.start
|
||||||
startPosBeforeDrag = existingRange.range.start
|
const endPosBeforeDrag = existingRange.range.end
|
||||||
endPosBeforeDrag = existingRange.range.end
|
let dragHappened = false
|
||||||
dragHappened = false
|
const sequence = val(layoutP.sheet).getSequence()
|
||||||
sequence = val(layoutP.sheet).getSequence()
|
isDraggingRef.current = true
|
||||||
target = event.target as HTMLDivElement
|
|
||||||
isDraggingRef.current = true
|
|
||||||
} else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onDrag(dx) {
|
|
||||||
existingRange = existingRangeD.getValue()
|
|
||||||
if (existingRange) {
|
|
||||||
dragHappened = true
|
|
||||||
const deltaPos = scaledSpaceToUnitSpace(dx)
|
|
||||||
|
|
||||||
const start = startPosBeforeDrag + deltaPos
|
return {
|
||||||
let end = endPosBeforeDrag + deltaPos
|
onDrag(dx) {
|
||||||
|
existingRange = existingRangeD.getValue()
|
||||||
|
if (existingRange) {
|
||||||
|
dragHappened = true
|
||||||
|
const deltaPos = scaledSpaceToUnitSpace(dx)
|
||||||
|
|
||||||
if (end < start) {
|
const start = startPosBeforeDrag + deltaPos
|
||||||
end = start
|
let end = endPosBeforeDrag + deltaPos
|
||||||
}
|
|
||||||
|
|
||||||
;[newStartPosition, newEndPosition] = clampRange(
|
if (end < start) {
|
||||||
[start, end],
|
end = start
|
||||||
[0, sequence.length],
|
}
|
||||||
).map((pos) => sequence.closestGridPosition(pos))
|
|
||||||
|
|
||||||
if (tempTransaction) {
|
;[newStartPosition, newEndPosition] = clampRange(
|
||||||
tempTransaction.discard()
|
[start, end],
|
||||||
}
|
[0, sequence.length],
|
||||||
|
).map((pos) => sequence.closestGridPosition(pos))
|
||||||
|
|
||||||
tempTransaction = getStudio().tempTransaction(({stateEditors}) => {
|
if (tempTransaction) {
|
||||||
stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.focusRange.set(
|
tempTransaction.discard()
|
||||||
{
|
}
|
||||||
...sheet.address,
|
|
||||||
range: {
|
tempTransaction = getStudio().tempTransaction(
|
||||||
start: newStartPosition,
|
({stateEditors}) => {
|
||||||
end: newEndPosition,
|
stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.focusRange.set(
|
||||||
|
{
|
||||||
|
...sheet.address,
|
||||||
|
range: {
|
||||||
|
start: newStartPosition,
|
||||||
|
end: newEndPosition,
|
||||||
|
},
|
||||||
|
enabled: existingRange?.enabled ?? true,
|
||||||
|
},
|
||||||
|
)
|
||||||
},
|
},
|
||||||
enabled: existingRange?.enabled ?? true,
|
)
|
||||||
},
|
}
|
||||||
)
|
},
|
||||||
})
|
onDragEnd() {
|
||||||
}
|
isDraggingRef.current = false
|
||||||
},
|
if (existingRange) {
|
||||||
onDragEnd() {
|
if (dragHappened && tempTransaction !== undefined) {
|
||||||
isDraggingRef.current = false
|
tempTransaction.commit()
|
||||||
if (existingRange) {
|
} else if (tempTransaction) {
|
||||||
if (dragHappened && tempTransaction !== undefined) {
|
tempTransaction.discard()
|
||||||
tempTransaction.commit()
|
}
|
||||||
} else if (tempTransaction) {
|
}
|
||||||
tempTransaction.discard()
|
},
|
||||||
}
|
|
||||||
tempTransaction = undefined
|
|
||||||
}
|
|
||||||
if (target !== undefined) {
|
|
||||||
target = undefined
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
lockCursorTo: 'grabbing',
|
lockCursorTo: 'grabbing',
|
||||||
}
|
}
|
||||||
}, [sheet, scaledSpaceToUnitSpace])
|
}, [sheet, scaledSpaceToUnitSpace])
|
||||||
|
|
|
@ -22,7 +22,6 @@ import {
|
||||||
useLockFrameStampPosition,
|
useLockFrameStampPosition,
|
||||||
} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||||
import {focusRangeStripTheme, RangeStrip} from './FocusRangeStrip'
|
import {focusRangeStripTheme, RangeStrip} from './FocusRangeStrip'
|
||||||
import type Sheet from '@theatre/core/sheets/Sheet'
|
|
||||||
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
|
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
|
||||||
|
|
||||||
const TheDiv = styled.div<{enabled: boolean; type: 'start' | 'end'}>`
|
const TheDiv = styled.div<{enabled: boolean; type: 'start' | 'end'}>`
|
||||||
|
@ -172,87 +171,84 @@ const FocusRangeThumb: React.FC<{
|
||||||
)
|
)
|
||||||
|
|
||||||
const gestureHandlers = useMemo((): Parameters<typeof useDrag>[1] => {
|
const gestureHandlers = useMemo((): Parameters<typeof useDrag>[1] => {
|
||||||
let defaultRange: IRange
|
|
||||||
let range: IRange
|
|
||||||
let focusRangeEnabled: boolean
|
|
||||||
let posBeforeDrag: number
|
|
||||||
let tempTransaction: CommitOrDiscard | undefined
|
|
||||||
let minFocusRangeStripWidth: number
|
|
||||||
let sheet: Sheet
|
|
||||||
let scaledSpaceToUnitSpace: (s: number) => number
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
debugName: 'FocusRangeThumb',
|
debugName: 'FocusRangeThumb',
|
||||||
onDragStart() {
|
onDragStart() {
|
||||||
sheet = val(layoutP.sheet)
|
let tempTransaction: CommitOrDiscard | undefined
|
||||||
|
let range: IRange
|
||||||
|
|
||||||
|
const sheet = val(layoutP.sheet)
|
||||||
const sequence = sheet.getSequence()
|
const sequence = sheet.getSequence()
|
||||||
defaultRange = {start: 0, end: sequence.length}
|
const defaultRange = {start: 0, end: sequence.length}
|
||||||
let existingRange = existingRangeD.getValue() || {
|
let existingRange = existingRangeD.getValue() || {
|
||||||
range: defaultRange,
|
range: defaultRange,
|
||||||
enabled: false,
|
enabled: false,
|
||||||
}
|
}
|
||||||
focusRangeEnabled = existingRange.enabled
|
const focusRangeEnabled = existingRange.enabled
|
||||||
|
|
||||||
posBeforeDrag = existingRange.range[thumbType]
|
const posBeforeDrag = existingRange.range[thumbType]
|
||||||
scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace)
|
const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace)
|
||||||
minFocusRangeStripWidth = scaledSpaceToUnitSpace(
|
const minFocusRangeStripWidth = scaledSpaceToUnitSpace(
|
||||||
focusRangeStripTheme.rangeStripMinWidth,
|
focusRangeStripTheme.rangeStripMinWidth,
|
||||||
)
|
)
|
||||||
},
|
|
||||||
onDrag(dx, _, event) {
|
return {
|
||||||
let newPosition: number
|
onDrag(dx, _, event) {
|
||||||
const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, {
|
let newPosition: number
|
||||||
ignore: hitZoneNode,
|
const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, {
|
||||||
})
|
ignore: hitZoneNode,
|
||||||
if (snapPos != null) {
|
})
|
||||||
newPosition = snapPos
|
if (snapPos != null) {
|
||||||
|
newPosition = snapPos
|
||||||
|
}
|
||||||
|
|
||||||
|
range = existingRangeD.getValue()?.range || defaultRange
|
||||||
|
const deltaPos = scaledSpaceToUnitSpace(dx)
|
||||||
|
const oldPosPlusDeltaPos = posBeforeDrag + deltaPos
|
||||||
|
// Make sure that the focus range has a minimal width
|
||||||
|
if (thumbType === 'start') {
|
||||||
|
// Prevent the start thumb from going below 0
|
||||||
|
newPosition = Math.max(
|
||||||
|
Math.min(
|
||||||
|
oldPosPlusDeltaPos,
|
||||||
|
range['end'] - minFocusRangeStripWidth,
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// Prevent the start thumb from going over the length of the sequence
|
||||||
|
newPosition = Math.min(
|
||||||
|
Math.max(
|
||||||
|
oldPosPlusDeltaPos,
|
||||||
|
range['start'] + minFocusRangeStripWidth,
|
||||||
|
),
|
||||||
|
sheet.getSequence().length,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const newPositionInFrame = sheet
|
||||||
|
.getSequence()
|
||||||
|
.closestGridPosition(newPosition)
|
||||||
|
|
||||||
|
if (tempTransaction !== undefined) {
|
||||||
|
tempTransaction.discard()
|
||||||
|
}
|
||||||
|
|
||||||
|
tempTransaction = getStudio().tempTransaction(({stateEditors}) => {
|
||||||
|
stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.focusRange.set(
|
||||||
|
{
|
||||||
|
...sheet.address,
|
||||||
|
range: {...range, [thumbType]: newPositionInFrame},
|
||||||
|
enabled: focusRangeEnabled,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onDragEnd(dragHappened) {
|
||||||
|
if (dragHappened) tempTransaction?.commit()
|
||||||
|
else tempTransaction?.discard()
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
range = existingRangeD.getValue()?.range || defaultRange
|
|
||||||
const deltaPos = scaledSpaceToUnitSpace(dx)
|
|
||||||
const oldPosPlusDeltaPos = posBeforeDrag + deltaPos
|
|
||||||
// Make sure that the focus range has a minimal width
|
|
||||||
if (thumbType === 'start') {
|
|
||||||
// Prevent the start thumb from going below 0
|
|
||||||
newPosition = Math.max(
|
|
||||||
Math.min(
|
|
||||||
oldPosPlusDeltaPos,
|
|
||||||
range['end'] - minFocusRangeStripWidth,
|
|
||||||
),
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// Prevent the start thumb from going over the length of the sequence
|
|
||||||
newPosition = Math.min(
|
|
||||||
Math.max(
|
|
||||||
oldPosPlusDeltaPos,
|
|
||||||
range['start'] + minFocusRangeStripWidth,
|
|
||||||
),
|
|
||||||
sheet.getSequence().length,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const newPositionInFrame = sheet
|
|
||||||
.getSequence()
|
|
||||||
.closestGridPosition(newPosition)
|
|
||||||
|
|
||||||
if (tempTransaction !== undefined) {
|
|
||||||
tempTransaction.discard()
|
|
||||||
}
|
|
||||||
|
|
||||||
tempTransaction = getStudio().tempTransaction(({stateEditors}) => {
|
|
||||||
stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.focusRange.set(
|
|
||||||
{
|
|
||||||
...sheet.address,
|
|
||||||
range: {...range, [thumbType]: newPositionInFrame},
|
|
||||||
enabled: focusRangeEnabled,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onDragEnd(dragHappened) {
|
|
||||||
if (dragHappened) tempTransaction?.commit()
|
|
||||||
else tempTransaction?.discard()
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [layoutP])
|
}, [layoutP])
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import type Sequence from '@theatre/core/sequences/Sequence'
|
|
||||||
import type Sheet from '@theatre/core/sheets/Sheet'
|
|
||||||
import type {Pointer} from '@theatre/dataverse'
|
import type {Pointer} from '@theatre/dataverse'
|
||||||
import {prism, val} from '@theatre/dataverse'
|
import {prism, val} from '@theatre/dataverse'
|
||||||
import {usePrism} from '@theatre/react'
|
import {usePrism} from '@theatre/react'
|
||||||
import type {$IntentionalAny, VoidFn} from '@theatre/shared/utils/types'
|
import type {$IntentionalAny} from '@theatre/shared/utils/types'
|
||||||
import getStudio from '@theatre/studio/getStudio'
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
import {
|
import {
|
||||||
panelDimsToPanelPosition,
|
panelDimsToPanelPosition,
|
||||||
|
@ -102,152 +100,150 @@ function usePanelDragZoneGestureHandlers(
|
||||||
const focusRangeCreationGestureHandlers = (): Parameters<
|
const focusRangeCreationGestureHandlers = (): Parameters<
|
||||||
typeof useDrag
|
typeof useDrag
|
||||||
>[1] => {
|
>[1] => {
|
||||||
let startPosInUnitSpace: number,
|
|
||||||
tempTransaction: CommitOrDiscard | undefined
|
|
||||||
|
|
||||||
let clippedSpaceToUnitSpace: (s: number) => number
|
|
||||||
let scaledSpaceToUnitSpace: (s: number) => number
|
|
||||||
let sequence: Sequence
|
|
||||||
let sheet: Sheet
|
|
||||||
let minFocusRangeStripWidth: number
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
debugName: 'FocusRangeZone/focusRangeCreationGestureHandlers',
|
debugName: 'FocusRangeZone/focusRangeCreationGestureHandlers',
|
||||||
onDragStart(event) {
|
onDragStart(event) {
|
||||||
clippedSpaceToUnitSpace = val(layoutP.clippedSpace.toUnitSpace)
|
let tempTransaction: CommitOrDiscard | undefined
|
||||||
scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace)
|
|
||||||
sheet = val(layoutP.sheet)
|
const clippedSpaceToUnitSpace = val(layoutP.clippedSpace.toUnitSpace)
|
||||||
sequence = sheet.getSequence()
|
const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace)
|
||||||
|
const sheet = val(layoutP.sheet)
|
||||||
|
const sequence = sheet.getSequence()
|
||||||
|
|
||||||
const targetElement: HTMLElement = event.target as HTMLElement
|
const targetElement: HTMLElement = event.target as HTMLElement
|
||||||
const rect = targetElement!.getBoundingClientRect()
|
const rect = targetElement!.getBoundingClientRect()
|
||||||
startPosInUnitSpace = clippedSpaceToUnitSpace(
|
const startPosInUnitSpace = clippedSpaceToUnitSpace(
|
||||||
event.clientX - rect.left,
|
event.clientX - rect.left,
|
||||||
)
|
)
|
||||||
minFocusRangeStripWidth = scaledSpaceToUnitSpace(
|
const minFocusRangeStripWidth = scaledSpaceToUnitSpace(
|
||||||
focusRangeStripTheme.rangeStripMinWidth,
|
focusRangeStripTheme.rangeStripMinWidth,
|
||||||
)
|
)
|
||||||
},
|
|
||||||
onDrag(dx) {
|
|
||||||
const deltaPos = scaledSpaceToUnitSpace(dx)
|
|
||||||
|
|
||||||
let start = startPosInUnitSpace
|
return {
|
||||||
let end = startPosInUnitSpace + deltaPos
|
onDrag(dx) {
|
||||||
|
const deltaPos = scaledSpaceToUnitSpace(dx)
|
||||||
|
|
||||||
;[start, end] = [
|
let start = startPosInUnitSpace
|
||||||
clamp(start, 0, sequence.length),
|
let end = startPosInUnitSpace + deltaPos
|
||||||
clamp(end, 0, sequence.length),
|
|
||||||
].map((pos) => sequence.closestGridPosition(pos))
|
|
||||||
|
|
||||||
if (end < start) {
|
;[start, end] = [
|
||||||
;[start, end] = [
|
clamp(start, 0, sequence.length),
|
||||||
Math.max(Math.min(end, start - minFocusRangeStripWidth), 0),
|
clamp(end, 0, sequence.length),
|
||||||
start,
|
].map((pos) => sequence.closestGridPosition(pos))
|
||||||
]
|
|
||||||
} else if (dx > 0) {
|
if (end < start) {
|
||||||
end = Math.min(
|
;[start, end] = [
|
||||||
Math.max(end, start + minFocusRangeStripWidth),
|
Math.max(Math.min(end, start - minFocusRangeStripWidth), 0),
|
||||||
sequence.length,
|
start,
|
||||||
)
|
]
|
||||||
|
} else if (dx > 0) {
|
||||||
|
end = Math.min(
|
||||||
|
Math.max(end, start + minFocusRangeStripWidth),
|
||||||
|
sequence.length,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tempTransaction) {
|
||||||
|
tempTransaction.discard()
|
||||||
|
}
|
||||||
|
|
||||||
|
tempTransaction = getStudio().tempTransaction(
|
||||||
|
({stateEditors}) => {
|
||||||
|
stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.focusRange.set(
|
||||||
|
{
|
||||||
|
...sheet.address,
|
||||||
|
range: {start, end},
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onDragEnd(dragHappened) {
|
||||||
|
if (dragHappened && tempTransaction !== undefined) {
|
||||||
|
tempTransaction.commit()
|
||||||
|
} else if (tempTransaction) {
|
||||||
|
tempTransaction.discard()
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tempTransaction) {
|
|
||||||
tempTransaction.discard()
|
|
||||||
}
|
|
||||||
|
|
||||||
tempTransaction = getStudio().tempTransaction(({stateEditors}) => {
|
|
||||||
stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.focusRange.set(
|
|
||||||
{
|
|
||||||
...sheet.address,
|
|
||||||
range: {start, end},
|
|
||||||
enabled: true,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
onDragEnd(dragHappened) {
|
|
||||||
if (dragHappened && tempTransaction !== undefined) {
|
|
||||||
tempTransaction.commit()
|
|
||||||
} else if (tempTransaction) {
|
|
||||||
tempTransaction.discard()
|
|
||||||
}
|
|
||||||
tempTransaction = undefined
|
|
||||||
},
|
},
|
||||||
|
|
||||||
lockCursorTo: 'ew-resize',
|
lockCursorTo: 'ew-resize',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const panelMoveGestureHandlers = (): Parameters<typeof useDrag>[1] => {
|
const panelMoveGestureHandlers = (): Parameters<typeof useDrag>[1] => {
|
||||||
let stuffBeforeDrag = panelStuffRef.current
|
|
||||||
let tempTransaction: CommitOrDiscard | undefined
|
|
||||||
let unlock: VoidFn | undefined
|
|
||||||
return {
|
return {
|
||||||
debugName: 'FocusRangeZone/panelMoveGestureHandlers',
|
debugName: 'FocusRangeZone/panelMoveGestureHandlers',
|
||||||
onDragStart() {
|
onDragStart() {
|
||||||
stuffBeforeDrag = panelStuffRef.current
|
let tempTransaction: CommitOrDiscard | undefined
|
||||||
if (unlock) {
|
const stuffBeforeDrag = panelStuffRef.current
|
||||||
const u = unlock
|
|
||||||
unlock = undefined
|
|
||||||
u()
|
|
||||||
}
|
|
||||||
unlock = panelStuffRef.current.addBoundsHighlightLock()
|
|
||||||
},
|
|
||||||
onDrag(dx, dy) {
|
|
||||||
const newDims: typeof panelStuffRef.current['dims'] = {
|
|
||||||
...stuffBeforeDrag.dims,
|
|
||||||
top: stuffBeforeDrag.dims.top + dy,
|
|
||||||
left: stuffBeforeDrag.dims.left + dx,
|
|
||||||
}
|
|
||||||
const position = panelDimsToPanelPosition(newDims, {
|
|
||||||
width: window.innerWidth,
|
|
||||||
height: window.innerHeight,
|
|
||||||
})
|
|
||||||
|
|
||||||
tempTransaction?.discard()
|
const unlock = panelStuffRef.current.addBoundsHighlightLock()
|
||||||
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
|
||||||
stateEditors.studio.historic.panelPositions.setPanelPosition({
|
return {
|
||||||
position,
|
onDrag(dx, dy) {
|
||||||
panelId: stuffBeforeDrag.panelId,
|
const newDims: typeof panelStuffRef.current['dims'] = {
|
||||||
})
|
...stuffBeforeDrag.dims,
|
||||||
})
|
top: stuffBeforeDrag.dims.top + dy,
|
||||||
},
|
left: stuffBeforeDrag.dims.left + dx,
|
||||||
onDragEnd(dragHappened) {
|
}
|
||||||
if (unlock) {
|
const position = panelDimsToPanelPosition(newDims, {
|
||||||
const u = unlock
|
width: window.innerWidth,
|
||||||
unlock = undefined
|
height: window.innerHeight,
|
||||||
u()
|
})
|
||||||
|
|
||||||
|
tempTransaction?.discard()
|
||||||
|
tempTransaction = getStudio()!.tempTransaction(
|
||||||
|
({stateEditors}) => {
|
||||||
|
stateEditors.studio.historic.panelPositions.setPanelPosition({
|
||||||
|
position,
|
||||||
|
panelId: stuffBeforeDrag.panelId,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onDragEnd(dragHappened) {
|
||||||
|
unlock()
|
||||||
|
if (dragHappened) {
|
||||||
|
tempTransaction?.commit()
|
||||||
|
} else {
|
||||||
|
tempTransaction?.discard()
|
||||||
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
if (dragHappened) {
|
|
||||||
tempTransaction?.commit()
|
|
||||||
} else {
|
|
||||||
tempTransaction?.discard()
|
|
||||||
}
|
|
||||||
tempTransaction = undefined
|
|
||||||
},
|
},
|
||||||
lockCursorTo: 'move',
|
lockCursorTo: 'move',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentGestureHandlers: undefined | Parameters<typeof useDrag>[1]
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
debugName: 'FocusRangeZone',
|
debugName: 'FocusRangeZone',
|
||||||
onDragStart(event) {
|
onDragStart(event) {
|
||||||
if (event.shiftKey) {
|
const [_mode, currentGestureHandlers] = event.shiftKey
|
||||||
setMode('creating')
|
? [
|
||||||
currentGestureHandlers = focusRangeCreationGestureHandlers()
|
'creating' as 'creating',
|
||||||
} else {
|
focusRangeCreationGestureHandlers().onDragStart(event),
|
||||||
setMode('moving-panel')
|
]
|
||||||
currentGestureHandlers = panelMoveGestureHandlers()
|
: [
|
||||||
|
'moving-panel' as 'moving-panel',
|
||||||
|
panelMoveGestureHandlers().onDragStart(event),
|
||||||
|
]
|
||||||
|
|
||||||
|
setMode(_mode)
|
||||||
|
|
||||||
|
if (currentGestureHandlers === false) return false
|
||||||
|
|
||||||
|
return {
|
||||||
|
onDrag(dx, dy, event, ddx, ddy) {
|
||||||
|
currentGestureHandlers.onDrag(dx, dy, event, ddx, ddy)
|
||||||
|
},
|
||||||
|
onDragEnd(dragHappened) {
|
||||||
|
setMode('none')
|
||||||
|
currentGestureHandlers.onDragEnd?.(dragHappened)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
currentGestureHandlers.onDragStart!(event)
|
|
||||||
},
|
|
||||||
onDrag(dx, dy, event) {
|
|
||||||
currentGestureHandlers!.onDrag(dx, dy, event)
|
|
||||||
},
|
|
||||||
onDragEnd(dragHappened) {
|
|
||||||
setMode('none')
|
|
||||||
currentGestureHandlers!.onDragEnd!(dragHappened)
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [layoutP, panelStuffRef])
|
}, [layoutP, panelStuffRef])
|
||||||
|
|
|
@ -247,46 +247,44 @@ function useDragMarker(
|
||||||
propsRef.current = props
|
propsRef.current = props
|
||||||
|
|
||||||
const useDragOpts = useMemo<UseDragOpts>(() => {
|
const useDragOpts = useMemo<UseDragOpts>(() => {
|
||||||
let toUnitSpace: SequenceEditorPanelLayout['scaledSpace']['toUnitSpace']
|
|
||||||
let tempTransaction: CommitOrDiscard | undefined
|
|
||||||
let markerAtStartOfDrag: StudioHistoricStateSequenceEditorMarker
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
debugName: `MarkerDot/useDragMarker (${props.marker.id})`,
|
debugName: `MarkerDot/useDragMarker (${props.marker.id})`,
|
||||||
|
|
||||||
onDragStart(_event) {
|
onDragStart(_event) {
|
||||||
markerAtStartOfDrag = propsRef.current.marker
|
const markerAtStartOfDrag = propsRef.current.marker
|
||||||
toUnitSpace = val(props.layoutP.scaledSpace.toUnitSpace)
|
const toUnitSpace = val(props.layoutP.scaledSpace.toUnitSpace)
|
||||||
},
|
let tempTransaction: CommitOrDiscard | undefined
|
||||||
onDrag(dx, _dy, event) {
|
|
||||||
const original = markerAtStartOfDrag
|
|
||||||
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
|
|
||||||
original.position + toUnitSpace(dx),
|
|
||||||
// sanitize to minimum of zero
|
|
||||||
0,
|
|
||||||
)
|
|
||||||
|
|
||||||
tempTransaction?.discard()
|
return {
|
||||||
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
onDrag(dx, _dy, event) {
|
||||||
stateEditors.studio.historic.projects.stateByProjectId.stateBySheetId.sequenceEditor.replaceMarkers(
|
const original = markerAtStartOfDrag
|
||||||
{
|
const newPosition = Math.max(
|
||||||
sheetAddress: val(props.layoutP.sheet.address),
|
// check if our event hoversover a [data-pos] element
|
||||||
markers: [{...original, position: newPosition}],
|
DopeSnap.checkIfMouseEventSnapToPos(event, {
|
||||||
snappingFunction: val(props.layoutP.sheet).getSequence()
|
ignore: node,
|
||||||
.closestGridPosition,
|
}) ??
|
||||||
},
|
// if we don't find snapping target, check the distance dragged + original position
|
||||||
)
|
original.position + toUnitSpace(dx),
|
||||||
})
|
// sanitize to minimum of zero
|
||||||
},
|
0,
|
||||||
onDragEnd(dragHappened) {
|
)
|
||||||
if (dragHappened) tempTransaction?.commit()
|
|
||||||
else tempTransaction?.discard()
|
tempTransaction?.discard()
|
||||||
tempTransaction = undefined
|
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
||||||
|
stateEditors.studio.historic.projects.stateByProjectId.stateBySheetId.sequenceEditor.replaceMarkers(
|
||||||
|
{
|
||||||
|
sheetAddress: val(props.layoutP.sheet.address),
|
||||||
|
markers: [{...original, position: newPosition}],
|
||||||
|
snappingFunction: val(props.layoutP.sheet).getSequence()
|
||||||
|
.closestGridPosition,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
onDragEnd(dragHappened) {
|
||||||
|
if (dragHappened) tempTransaction?.commit()
|
||||||
|
else tempTransaction?.discard()
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import type Sequence from '@theatre/core/sequences/Sequence'
|
|
||||||
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||||
import RoomToClick from '@theatre/studio/uiComponents/RoomToClick'
|
import RoomToClick from '@theatre/studio/uiComponents/RoomToClick'
|
||||||
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
import useDrag from '@theatre/studio/uiComponents/useDrag'
|
||||||
|
@ -203,32 +202,31 @@ const Playhead: React.FC<{layoutP: Pointer<SequenceEditorPanelLayout>}> = ({
|
||||||
)
|
)
|
||||||
|
|
||||||
const gestureHandlers = useMemo((): Parameters<typeof useDrag>[1] => {
|
const gestureHandlers = useMemo((): Parameters<typeof useDrag>[1] => {
|
||||||
const setIsSeeking = val(layoutP.seeker.setIsSeeking)
|
|
||||||
|
|
||||||
let posBeforeSeek = 0
|
|
||||||
let sequence: Sequence
|
|
||||||
let scaledSpaceToUnitSpace: typeof layoutP.scaledSpace.toUnitSpace.$$__pointer_type
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
debugName: 'Playhead',
|
debugName: 'Playhead',
|
||||||
onDragStart() {
|
onDragStart() {
|
||||||
sequence = val(layoutP.sheet).getSequence()
|
const setIsSeeking = val(layoutP.seeker.setIsSeeking)
|
||||||
posBeforeSeek = sequence.position
|
|
||||||
scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace)
|
|
||||||
setIsSeeking(true)
|
|
||||||
},
|
|
||||||
onDrag(dx, _, event) {
|
|
||||||
const deltaPos = scaledSpaceToUnitSpace(dx)
|
|
||||||
|
|
||||||
sequence.position =
|
const sequence = val(layoutP.sheet).getSequence()
|
||||||
DopeSnap.checkIfMouseEventSnapToPos(event, {
|
const posBeforeSeek = sequence.position
|
||||||
ignore: thumbNode,
|
const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace)
|
||||||
}) ??
|
setIsSeeking(true)
|
||||||
// unsnapped
|
|
||||||
clamp(posBeforeSeek + deltaPos, 0, sequence.length)
|
return {
|
||||||
},
|
onDrag(dx, _, event) {
|
||||||
onDragEnd() {
|
const deltaPos = scaledSpaceToUnitSpace(dx)
|
||||||
setIsSeeking(false)
|
|
||||||
|
sequence.position =
|
||||||
|
DopeSnap.checkIfMouseEventSnapToPos(event, {
|
||||||
|
ignore: thumbNode,
|
||||||
|
}) ??
|
||||||
|
// unsnapped
|
||||||
|
clamp(posBeforeSeek + deltaPos, 0, sequence.length)
|
||||||
|
},
|
||||||
|
onDragEnd() {
|
||||||
|
setIsSeeking(false)
|
||||||
|
},
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
|
@ -4,6 +4,28 @@ import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
||||||
import {useCssCursorLock} from './PointerEventsHandler'
|
import {useCssCursorLock} from './PointerEventsHandler'
|
||||||
import type {CapturedPointer} from '@theatre/studio/UIRoot/PointerCapturing'
|
import type {CapturedPointer} from '@theatre/studio/UIRoot/PointerCapturing'
|
||||||
import {usePointerCapturing} from '@theatre/studio/UIRoot/PointerCapturing'
|
import {usePointerCapturing} from '@theatre/studio/UIRoot/PointerCapturing'
|
||||||
|
import noop from '@theatre/shared/utils/noop'
|
||||||
|
|
||||||
|
export enum MouseButton {
|
||||||
|
Left = 0,
|
||||||
|
Middle = 1,
|
||||||
|
// Not including Right because it _might_ interfere with chord clicking.
|
||||||
|
// So we'll wait for chord-clicking to land before exploring right-button gestures
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* dx, dy: delta x/y from the start of the drag
|
||||||
|
*/
|
||||||
|
type OnDragCallback = (
|
||||||
|
// deltaX/Y are counted from the start of the drag
|
||||||
|
deltaX: number,
|
||||||
|
deltaY: number,
|
||||||
|
event: MouseEvent,
|
||||||
|
dxFromLastEvent: number,
|
||||||
|
dyFromLastEvent: number,
|
||||||
|
) => void
|
||||||
|
|
||||||
|
type OnDragEndCallback = (dragHappened: boolean) => void
|
||||||
|
|
||||||
export type UseDragOpts = {
|
export type UseDragOpts = {
|
||||||
/**
|
/**
|
||||||
|
@ -35,22 +57,23 @@ export type UseDragOpts = {
|
||||||
* onDragStart can be undefined, in which case, we always handle useDrag,
|
* onDragStart can be undefined, in which case, we always handle useDrag,
|
||||||
* but when defined, we can allow the handler to return false to indicate ignore this dragging
|
* but when defined, we can allow the handler to return false to indicate ignore this dragging
|
||||||
*/
|
*/
|
||||||
onDragStart?: (event: MouseEvent) => void | false
|
onDragStart: (event: MouseEvent) =>
|
||||||
/**
|
| false
|
||||||
* Called at the end of the drag gesture.
|
| {
|
||||||
* `dragHappened` will be `true` if the user actually moved the pointer
|
/**
|
||||||
* (if onDrag isn't called, then this will be false becuase the user hasn't moved the pointer)
|
* Called at the end of the drag gesture.
|
||||||
*/
|
* `dragHappened` will be `true` if the user actually moved the pointer
|
||||||
onDragEnd?: (dragHappened: boolean) => void
|
* (if onDrag isn't called, then this will be false becuase the user hasn't moved the pointer)
|
||||||
/**
|
*/
|
||||||
* This will be called 0 times if the gesture ends up being a click,
|
onDragEnd?: OnDragEndCallback
|
||||||
* or 1 or more times if it ends up being a drag gesture.
|
onDrag: OnDragCallback
|
||||||
*
|
}
|
||||||
* `dx`: the delta x
|
|
||||||
* `dy`: the delta y
|
// which mouse button to use the drag event
|
||||||
* `event`: the mouse event
|
buttons?:
|
||||||
*/
|
| [MouseButton]
|
||||||
onDrag: (dx: number, dy: number, event: MouseEvent) => void
|
| [MouseButton, MouseButton]
|
||||||
|
| [MouseButton | MouseButton | MouseButton]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function useDrag(
|
export default function useDrag(
|
||||||
|
@ -80,9 +103,15 @@ export default function useDrag(
|
||||||
}
|
}
|
||||||
}>({dragHappened: false, startPos: {x: 0, y: 0}})
|
}>({dragHappened: false, startPos: {x: 0, y: 0}})
|
||||||
|
|
||||||
|
const callbacksRef = useRef<{
|
||||||
|
onDrag: OnDragCallback
|
||||||
|
onDragEnd: OnDragEndCallback
|
||||||
|
}>({onDrag: noop, onDragEnd: noop})
|
||||||
|
|
||||||
const capturedPointerRef = useRef<undefined | CapturedPointer>()
|
const capturedPointerRef = useRef<undefined | CapturedPointer>()
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
if (!target) return
|
if (!target) return
|
||||||
|
let lastDeltas = [0, 0]
|
||||||
|
|
||||||
const getDistances = (event: MouseEvent): [number, number] => {
|
const getDistances = (event: MouseEvent): [number, number] => {
|
||||||
const {startPos} = stateRef.current
|
const {startPos} = stateRef.current
|
||||||
|
@ -94,14 +123,26 @@ export default function useDrag(
|
||||||
modeRef.current = 'dragging'
|
modeRef.current = 'dragging'
|
||||||
|
|
||||||
const deltas = getDistances(event)
|
const deltas = getDistances(event)
|
||||||
optsRef.current.onDrag(deltas[0], deltas[1], event)
|
const [deltaFromLastX, deltaFromLastY] = [
|
||||||
|
deltas[0] - lastDeltas[0],
|
||||||
|
deltas[1] - lastDeltas[1],
|
||||||
|
]
|
||||||
|
lastDeltas = deltas
|
||||||
|
|
||||||
|
callbacksRef.current.onDrag(
|
||||||
|
deltas[0],
|
||||||
|
deltas[1],
|
||||||
|
event,
|
||||||
|
deltaFromLastX,
|
||||||
|
deltaFromLastY,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const dragEndHandler = () => {
|
const dragEndHandler = () => {
|
||||||
removeDragListeners()
|
removeDragListeners()
|
||||||
modeRef.current = 'notDragging'
|
modeRef.current = 'notDragging'
|
||||||
|
|
||||||
optsRef.current.onDragEnd?.(stateRef.current.dragHappened)
|
callbacksRef.current.onDragEnd(stateRef.current.dragHappened)
|
||||||
}
|
}
|
||||||
|
|
||||||
const addDragListeners = () => {
|
const addDragListeners = () => {
|
||||||
|
@ -136,13 +177,18 @@ export default function useDrag(
|
||||||
const opts = optsRef.current
|
const opts = optsRef.current
|
||||||
if (opts.disabled === true) return
|
if (opts.disabled === true) return
|
||||||
|
|
||||||
if (event.button !== 0) return
|
const acceptedButtons: MouseButton[] = opts.buttons ?? [MouseButton.Left]
|
||||||
|
|
||||||
// onDragStart can be undefined, in which case, we always handle useDrag,
|
if (!acceptedButtons.includes(event.button)) return
|
||||||
// but when defined, we can allow the handler to return false to indicate ignore this dragging
|
|
||||||
if (opts.onDragStart != null) {
|
const returnOfOnDragStart = opts.onDragStart(event)
|
||||||
const shouldIgnore = opts.onDragStart(event) === false
|
|
||||||
if (shouldIgnore) return
|
if (returnOfOnDragStart === false) {
|
||||||
|
// we should ignore the gesture
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
callbacksRef.current.onDrag = returnOfOnDragStart.onDrag
|
||||||
|
callbacksRef.current.onDragEnd = returnOfOnDragStart.onDragEnd ?? noop
|
||||||
}
|
}
|
||||||
|
|
||||||
// need to capture pointer after we know the provided handler wants to handle drag start
|
// need to capture pointer after we know the provided handler wants to handle drag start
|
||||||
|
@ -175,7 +221,7 @@ export default function useDrag(
|
||||||
target.removeEventListener('click', preventUnwantedClick as $FixMe)
|
target.removeEventListener('click', preventUnwantedClick as $FixMe)
|
||||||
|
|
||||||
if (modeRef.current !== 'notDragging') {
|
if (modeRef.current !== 'notDragging') {
|
||||||
optsRef.current.onDragEnd?.(modeRef.current === 'dragging')
|
callbacksRef.current.onDragEnd?.(modeRef.current === 'dragging')
|
||||||
}
|
}
|
||||||
modeRef.current = 'notDragging'
|
modeRef.current = 'notDragging'
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue