Simplify useDrag()'s API and implement middle-button dragging in SequenceEditor

This commit is contained in:
Aria Minaei 2022-05-10 15:30:10 +02:00
parent e12d495f29
commit 202b61c48c
17 changed files with 995 additions and 972 deletions

View file

@ -21,21 +21,16 @@ 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 const unlock = panelStuff.addBoundsHighlightLock()
u()
} return {
unlock = panelStuff.addBoundsHighlightLock()
},
onDrag(dx, dy) { onDrag(dx, dy) {
const newDims: typeof panelStuff['dims'] = { const newDims: typeof panelStuff['dims'] = {
...stuffBeforeDrag.dims, ...stuffBeforeDrag.dims,
@ -56,17 +51,14 @@ const PanelDragZone: React.FC<
}) })
}, },
onDragEnd(dragHappened) { onDragEnd(dragHappened) {
if (unlock) { unlock()
const u = unlock
unlock = undefined
u()
}
if (dragHappened) { if (dragHappened) {
tempTransaction?.commit() tempTransaction?.commit()
} else { } else {
tempTransaction?.discard() tempTransaction?.discard()
} }
tempTransaction = undefined },
}
}, },
} }
}, []) }, [])

View file

@ -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,27 +142,19 @@ 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 stuffBeforeDrag = panelStuffRef.current
const u = unlock const unlock = panelStuff.addBoundsHighlightLock()
unlock = undefined
u() return {
}
unlock = panelStuff.addBoundsHighlightLock()
},
onDrag(dx, dy) { onDrag(dx, dy) {
const newDims: typeof panelStuff['dims'] = { const newDims: typeof panelStuff['dims'] = {
...stuffBeforeDrag.dims, ...stuffBeforeDrag.dims,
@ -215,23 +207,19 @@ const PanelResizeHandle: React.FC<{
}) })
}, },
onDragEnd(dragHappened) { onDragEnd(dragHappened) {
if (unlock) { unlock()
const u = unlock
unlock = undefined
u()
}
setIsDragging(false)
if (dragHappened) { if (dragHappened) {
tempTransaction?.commit() tempTransaction?.commit()
} else { } else {
tempTransaction?.discard() 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

View file

@ -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,22 +151,18 @@ 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
.getDragHandlers({
...sheetObject.address, ...sheetObject.address,
pathToProp: leaf.pathToProp, pathToProp: leaf.pathToProp,
trackId: leaf.trackId, trackId: leaf.trackId,
@ -180,20 +171,18 @@ function useDragKeyframe(node: HTMLDivElement | null, props: IProps) {
positionAtStartOfDrag: positionAtStartOfDrag:
props.trackData.keyframes[props.index].position, props.trackData.keyframes[props.index].position,
}) })
selectionDragHandlers.onDragStart?.(event) .onDragStart(event)
return
} }
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,
)
return {
onDrag(dx, dy, event) { onDrag(dx, dy, event) {
if (selectionDragHandlers) {
selectionDragHandlers.onDrag(dx, dy, event)
return
}
const delta = toUnitSpace(dx) const delta = toUnitSpace(dx)
if (tempTransaction) { if (tempTransaction) {
tempTransaction.discard() tempTransaction.discard()
@ -219,11 +208,6 @@ function useDragKeyframe(node: HTMLDivElement | null, props: IProps) {
}) })
}, },
onDragEnd(dragHappened) { onDragEnd(dragHappened) {
if (selectionDragHandlers) {
selectionDragHandlers.onDragEnd?.(dragHappened)
selectionDragHandlers = undefined
}
if (dragHappened) { if (dragHappened) {
if (tempTransaction) { if (tempTransaction) {
tempTransaction.commit() tempTransaction.commit()
@ -233,7 +217,8 @@ function useDragKeyframe(node: HTMLDivElement | null, props: IProps) {
tempTransaction.discard() tempTransaction.discard()
} }
} }
tempTransaction = undefined },
}
}, },
} }
}, []) }, [])

View file

@ -256,7 +256,7 @@ 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
@ -266,6 +266,8 @@ function useKeyframeDrag(
setFrozen(false) setFrozen(false)
props.onCancelCurveChange() props.onCancelCurveChange()
}, },
}
},
}), }),
[svgNode, props.trackData], [svgNode, props.trackData],
) )

View file

@ -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,7 +152,8 @@ 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
.getDragHandlers({
...sheetObject.address, ...sheetObject.address,
pathToProp: leaf.pathToProp, pathToProp: leaf.pathToProp,
trackId: leaf.trackId, trackId: leaf.trackId,
@ -173,19 +162,18 @@ function useDragKeyframe(
positionAtStartOfDrag: positionAtStartOfDrag:
props.trackData.keyframes[props.index].position, props.trackData.keyframes[props.index].position,
}) })
selectionDragHandlers.onDragStart?.(event) .onDragStart(event)
return
} }
propsAtStartOfDrag = props const propsAtStartOfDrag = props
toUnitSpace = val(propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace) const toUnitSpace = val(
}, propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace,
)
let tempTransaction: CommitOrDiscard | undefined
return {
onDrag(dx, dy, event) { onDrag(dx, dy, event) {
if (selectionDragHandlers) {
selectionDragHandlers.onDrag(dx, dy, event)
return
}
const original = const original =
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index] propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index]
const newPosition = Math.max( const newPosition = Math.max(
@ -215,14 +203,10 @@ function useDragKeyframe(
}) })
}, },
onDragEnd(dragHappened) { onDragEnd(dragHappened) {
if (selectionDragHandlers) {
selectionDragHandlers.onDragEnd?.(dragHappened)
selectionDragHandlers = undefined
}
if (dragHappened) tempTransaction?.commit() if (dragHappened) tempTransaction?.commit()
else tempTransaction?.discard() else tempTransaction?.discard()
tempTransaction = undefined },
}
}, },
} }
}, []) }, [])

View file

@ -79,7 +79,8 @@ function useCaptureSelection(
} }
val(layoutP.selectionAtom).setState({current: undefined}) val(layoutP.selectionAtom).setState({current: undefined})
},
return {
onDrag(_dx, _dy, event) { onDrag(_dx, _dy, event) {
// const state = ref.current! // const state = ref.current!
const rect = containerNode!.getBoundingClientRect() const rect = containerNode!.getBoundingClientRect()
@ -102,6 +103,8 @@ function useCaptureSelection(
ref.current = null ref.current = null
}, },
} }
},
}
}, [layoutP, containerNode, ref]), }, [layoutP, containerNode, ref]),
) )
@ -183,14 +186,14 @@ 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
},
const toUnitSpace = val(layoutP.scaledSpace.toUnitSpace)
return {
onDrag(dx, _, event) { onDrag(dx, _, event) {
if (tempTransaction) { if (tempTransaction) {
tempTransaction.discard() tempTransaction.discard()
@ -208,12 +211,15 @@ namespace utils {
delta = toUnitSpace(dx) delta = toUnitSpace(dx)
} }
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => { tempTransaction = getStudio()!.tempTransaction(
({stateEditors}) => {
const transformKeyframes = const transformKeyframes =
stateEditors.coreByProject.historic.sheetsById.sequence stateEditors.coreByProject.historic.sheetsById.sequence
.transformKeyframes .transformKeyframes
for (const objectKey of Object.keys(selection.byObjectKey)) { for (const objectKey of Object.keys(
selection.byObjectKey,
)) {
const {byTrackId} = selection.byObjectKey[objectKey]! const {byTrackId} = selection.byObjectKey[objectKey]!
for (const trackId of Object.keys(byTrackId)) { for (const trackId of Object.keys(byTrackId)) {
const {byKeyframeId} = byTrackId[trackId]! const {byKeyframeId} = byTrackId[trackId]!
@ -223,19 +229,22 @@ namespace utils {
translate: delta, translate: delta,
scale: 1, scale: 1,
origin: 0, origin: 0,
snappingFunction: sheet.getSequence().closestGridPosition, snappingFunction:
sheet.getSequence().closestGridPosition,
objectKey, objectKey,
projectId: origin.projectId, projectId: origin.projectId,
sheetId: origin.sheetId, sheetId: origin.sheetId,
}) })
} }
} }
}) },
)
}, },
onDragEnd(dragHappened) { onDragEnd(dragHappened) {
if (dragHappened) tempTransaction?.commit() if (dragHappened) tempTransaction?.commit()
else tempTransaction?.discard() else tempTransaction?.discard()
tempTransaction = undefined },
}
}, },
} }
}, },

View file

@ -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,18 +105,40 @@ 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)
return {
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() { onDragEnd() {
setIsSeeking(false) setIsSeeking(false)
}, },
} }
},
}
}, [layoutP, containerEl]) }, [layoutP, containerEl])
const [isDragging] = useDrag(containerEl, handlers) const [isDragging] = useDrag(containerEl, handlers)
@ -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]) {

View file

@ -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,28 +218,23 @@ 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)
const initialLength = sheet.getSequence().length
const toUnitSpace = val(
propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace,
)
return {
onDrag(dx, dy, event) { onDrag(dx, dy, event) {
const delta = toUnitSpace(dx) const delta = toUnitSpace(dx)
if (tempTransaction) { if (tempTransaction) {
@ -248,14 +242,15 @@ function useDragBulge(
tempTransaction = undefined tempTransaction = undefined
} }
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => { tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
stateEditors.coreByProject.historic.sheetsById.sequence.setLength({ stateEditors.coreByProject.historic.sheetsById.sequence.setLength(
{
...sheet.address, ...sheet.address,
length: initialLength + delta, length: initialLength + delta,
}) },
)
}) })
}, },
onDragEnd(dragHappened) { onDragEnd(dragHappened) {
setIsDragging(false)
if (dragHappened) { if (dragHappened) {
if (tempTransaction) { if (tempTransaction) {
tempTransaction.commit() tempTransaction.commit()
@ -265,12 +260,14 @@ function useDragBulge(
tempTransaction.discard() tempTransaction.discard()
} }
} }
tempTransaction = undefined },
}
}, },
} }
}, []) }, [])
useDrag(node, gestureHandlers) const [isDragging] = useDrag(node, gestureHandlers)
useLockFrameStampPosition(isDragging, -1)
return [isDragging] return [isDragging]
} }

View file

@ -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,26 +123,24 @@ 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()
},
return {
onDrag(dxInScaledSpace, dy) { onDrag(dxInScaledSpace, dy) {
if (tempTransaction) { if (tempTransaction) {
tempTransaction.discard() tempTransaction.discard()
@ -163,17 +159,25 @@ function useOurDrags(node: SVGCircleElement | null, props: IProps): void {
const dYInExtremumSpace = verticalToExtremumSpace(dyInVerticalSpace) const dYInExtremumSpace = verticalToExtremumSpace(dyInVerticalSpace)
const dYInValueSpace = const dYInValueSpace =
propsAtStartOfDrag.extremumSpace.deltaToValueSpace(dYInExtremumSpace) propsAtStartOfDrag.extremumSpace.deltaToValueSpace(
dYInExtremumSpace,
)
const curValue = props.isScalar ? (cur.value as number) : 0 const curValue = props.isScalar ? (cur.value as number) : 0
const nextValue = props.isScalar ? (next.value as number) : 1 const nextValue = props.isScalar ? (next.value as number) : 1
const dyInKeyframeDiffSpace = dYInValueSpace / (nextValue - curValue) const dyInKeyframeDiffSpace =
dYInValueSpace / (nextValue - curValue)
if (propsAtStartOfDrag.which === 'left') { if (propsAtStartOfDrag.which === 'left') {
const handleX = clamp(cur.handles[2] + dPosInKeyframeDiffSpace, 0, 1) const handleX = clamp(
cur.handles[2] + dPosInKeyframeDiffSpace,
0,
1,
)
const handleY = cur.handles[3] + dyInKeyframeDiffSpace const handleY = cur.handles[3] + dyInKeyframeDiffSpace
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,
@ -184,17 +188,28 @@ function useOurDrags(node: SVGCircleElement | null, props: IProps): void {
keyframes: [ keyframes: [
{ {
...cur, ...cur,
handles: [cur.handles[0], cur.handles[1], handleX, handleY], handles: [
cur.handles[0],
cur.handles[1],
handleX,
handleY,
],
}, },
], ],
}, },
) )
}) },
)
} else { } else {
const handleX = clamp(next.handles[0] + dPosInKeyframeDiffSpace, 0, 1) const handleX = clamp(
next.handles[0] + dPosInKeyframeDiffSpace,
0,
1,
)
const handleY = next.handles[1] + dyInKeyframeDiffSpace const handleY = next.handles[1] + dyInKeyframeDiffSpace
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,
@ -215,15 +230,12 @@ function useOurDrags(node: SVGCircleElement | null, props: IProps): void {
], ],
}, },
) )
}) },
)
} }
}, },
onDragEnd(dragHappened) { onDragEnd(dragHappened) {
if (unlockExtremums) { unlockExtremums()
const unlock = unlockExtremums
unlockExtremums = undefined
unlock()
}
if (dragHappened) { if (dragHappened) {
if (tempTransaction) { if (tempTransaction) {
tempTransaction.commit() tempTransaction.commit()
@ -233,7 +245,8 @@ function useOurDrags(node: SVGCircleElement | null, props: IProps): void {
tempTransaction.discard() tempTransaction.discard()
} }
} }
tempTransaction = undefined },
}
}, },
} }
}, []) }, [])

View file

@ -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,24 +105,21 @@ 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]
@ -158,17 +153,14 @@ function useDragKeyframe(
}, },
onDragEnd(dragHappened) { onDragEnd(dragHappened) {
setIsDragging(false) setIsDragging(false)
if (unlockExtremums) { unlockExtremums()
const unlock = unlockExtremums
unlockExtremums = undefined
unlock()
}
if (dragHappened) { if (dragHappened) {
tempTransaction?.commit() tempTransaction?.commit()
} else { } else {
tempTransaction?.discard() tempTransaction?.discard()
} }
tempTransaction = undefined },
}
}, },
} }
}, []) }, [])

View file

@ -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,29 +106,25 @@ 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
return {
onDrag(dx, dy) { onDrag(dx, dy) {
const original = const original =
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index] propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index]
@ -140,7 +134,9 @@ function useDragKeyframe(
const dYInExtremumSpace = verticalToExtremumSpace(dyInVerticalSpace) const dYInExtremumSpace = verticalToExtremumSpace(dyInVerticalSpace)
const dYInValueSpace = const dYInValueSpace =
propsAtStartOfDrag.extremumSpace.deltaToValueSpace(dYInExtremumSpace) propsAtStartOfDrag.extremumSpace.deltaToValueSpace(
dYInExtremumSpace,
)
const updatedKeyframes: Keyframe[] = [] const updatedKeyframes: Keyframe[] = []
@ -155,11 +151,14 @@ function useDragKeyframe(
if (keepSpeeds) { if (keepSpeeds) {
const prev = const prev =
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index - 1] propsAtStartOfDrag.trackData.keyframes[
propsAtStartOfDrag.index - 1
]
if ( if (
prev && prev &&
Math.abs((original.value as number) - (prev.value as number)) > 0 Math.abs((original.value as number) - (prev.value as number)) >
0
) { ) {
const newPrev: Keyframe = { const newPrev: Keyframe = {
...prev, ...prev,
@ -175,11 +174,14 @@ function useDragKeyframe(
) )
} }
const next = const next =
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index + 1] propsAtStartOfDrag.trackData.keyframes[
propsAtStartOfDrag.index + 1
]
if ( if (
next && next &&
Math.abs((original.value as number) - (next.value as number)) > 0 Math.abs((original.value as number) - (next.value as number)) >
0
) { ) {
const newNext: Keyframe = { const newNext: Keyframe = {
...next, ...next,
@ -212,17 +214,14 @@ function useDragKeyframe(
}, },
onDragEnd(dragHappened) { onDragEnd(dragHappened) {
setIsDragging(false) setIsDragging(false)
if (unlockExtremums) { unlockExtremums()
const unlock = unlockExtremums
unlockExtremums = undefined
unlock()
}
if (dragHappened) { if (dragHappened) {
tempTransaction?.commit() tempTransaction?.commit()
} else { } else {
tempTransaction?.discard() tempTransaction?.discard()
} }
tempTransaction = undefined },
}
}, },
} }
}, []) }, [])

View file

@ -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,31 +179,22 @@ 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()
target = event.target as HTMLDivElement
isDraggingRef.current = true isDraggingRef.current = true
} else {
return false return {
}
},
onDrag(dx) { onDrag(dx) {
existingRange = existingRangeD.getValue() existingRange = existingRangeD.getValue()
if (existingRange) { if (existingRange) {
@ -226,7 +217,8 @@ const FocusRangeStrip: React.FC<{
tempTransaction.discard() tempTransaction.discard()
} }
tempTransaction = getStudio().tempTransaction(({stateEditors}) => { tempTransaction = getStudio().tempTransaction(
({stateEditors}) => {
stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.focusRange.set( stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.focusRange.set(
{ {
...sheet.address, ...sheet.address,
@ -237,7 +229,8 @@ const FocusRangeStrip: React.FC<{
enabled: existingRange?.enabled ?? true, enabled: existingRange?.enabled ?? true,
}, },
) )
}) },
)
} }
}, },
onDragEnd() { onDragEnd() {
@ -248,12 +241,11 @@ const FocusRangeStrip: React.FC<{
} else if (tempTransaction) { } else if (tempTransaction) {
tempTransaction.discard() tempTransaction.discard()
} }
tempTransaction = undefined
}
if (target !== undefined) {
target = undefined
} }
}, },
}
},
lockCursorTo: 'grabbing', lockCursorTo: 'grabbing',
} }
}, [sheet, scaledSpaceToUnitSpace]) }, [sheet, scaledSpaceToUnitSpace])

View file

@ -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,33 +171,28 @@ 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,
) )
},
return {
onDrag(dx, _, event) { onDrag(dx, _, event) {
let newPosition: number let newPosition: number
const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, { const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, {
@ -255,6 +249,8 @@ const FocusRangeThumb: React.FC<{
else tempTransaction?.discard() else tempTransaction?.discard()
}, },
} }
},
}
}, [layoutP]) }, [layoutP])
const [isDragging] = useDrag(hitZoneNode, gestureHandlers) const [isDragging] = useDrag(hitZoneNode, gestureHandlers)

View file

@ -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,32 +100,26 @@ 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,
) )
},
return {
onDrag(dx) { onDrag(dx) {
const deltaPos = scaledSpaceToUnitSpace(dx) const deltaPos = scaledSpaceToUnitSpace(dx)
@ -155,7 +147,8 @@ function usePanelDragZoneGestureHandlers(
tempTransaction.discard() tempTransaction.discard()
} }
tempTransaction = getStudio().tempTransaction(({stateEditors}) => { tempTransaction = getStudio().tempTransaction(
({stateEditors}) => {
stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.focusRange.set( stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.focusRange.set(
{ {
...sheet.address, ...sheet.address,
@ -163,7 +156,8 @@ function usePanelDragZoneGestureHandlers(
enabled: true, enabled: true,
}, },
) )
}) },
)
}, },
onDragEnd(dragHappened) { onDragEnd(dragHappened) {
if (dragHappened && tempTransaction !== undefined) { if (dragHappened && tempTransaction !== undefined) {
@ -171,27 +165,24 @@ function usePanelDragZoneGestureHandlers(
} else if (tempTransaction) { } else if (tempTransaction) {
tempTransaction.discard() 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 const unlock = panelStuffRef.current.addBoundsHighlightLock()
u()
} return {
unlock = panelStuffRef.current.addBoundsHighlightLock()
},
onDrag(dx, dy) { onDrag(dx, dy) {
const newDims: typeof panelStuffRef.current['dims'] = { const newDims: typeof panelStuffRef.current['dims'] = {
...stuffBeforeDrag.dims, ...stuffBeforeDrag.dims,
@ -204,50 +195,55 @@ function usePanelDragZoneGestureHandlers(
}) })
tempTransaction?.discard() tempTransaction?.discard()
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => { tempTransaction = getStudio()!.tempTransaction(
({stateEditors}) => {
stateEditors.studio.historic.panelPositions.setPanelPosition({ stateEditors.studio.historic.panelPositions.setPanelPosition({
position, position,
panelId: stuffBeforeDrag.panelId, panelId: stuffBeforeDrag.panelId,
}) })
}) },
)
}, },
onDragEnd(dragHappened) { onDragEnd(dragHappened) {
if (unlock) { unlock()
const u = unlock
unlock = undefined
u()
}
if (dragHappened) { if (dragHappened) {
tempTransaction?.commit() tempTransaction?.commit()
} else { } else {
tempTransaction?.discard() 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',
currentGestureHandlers.onDragStart!(event) panelMoveGestureHandlers().onDragStart(event),
}, ]
onDrag(dx, dy, event) {
currentGestureHandlers!.onDrag(dx, dy, event) setMode(_mode)
if (currentGestureHandlers === false) return false
return {
onDrag(dx, dy, event, ddx, ddy) {
currentGestureHandlers.onDrag(dx, dy, event, ddx, ddy)
}, },
onDragEnd(dragHappened) { onDragEnd(dragHappened) {
setMode('none') setMode('none')
currentGestureHandlers!.onDragEnd!(dragHappened) currentGestureHandlers.onDragEnd?.(dragHappened)
},
}
}, },
} }
}, [layoutP, panelStuffRef]) }, [layoutP, panelStuffRef])

View file

@ -247,17 +247,14 @@ 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
return {
onDrag(dx, _dy, event) { onDrag(dx, _dy, event) {
const original = markerAtStartOfDrag const original = markerAtStartOfDrag
const newPosition = Math.max( const newPosition = Math.max(
@ -286,7 +283,8 @@ function useDragMarker(
onDragEnd(dragHappened) { onDragEnd(dragHappened) {
if (dragHappened) tempTransaction?.commit() if (dragHappened) tempTransaction?.commit()
else tempTransaction?.discard() else tempTransaction?.discard()
tempTransaction = undefined },
}
}, },
} }
}, []) }, [])

View file

@ -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,20 +202,17 @@ 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) const sequence = val(layoutP.sheet).getSequence()
const posBeforeSeek = sequence.position
const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace)
setIsSeeking(true) setIsSeeking(true)
},
return {
onDrag(dx, _, event) { onDrag(dx, _, event) {
const deltaPos = scaledSpaceToUnitSpace(dx) const deltaPos = scaledSpaceToUnitSpace(dx)
@ -231,6 +227,8 @@ const Playhead: React.FC<{layoutP: Pointer<SequenceEditorPanelLayout>}> = ({
setIsSeeking(false) setIsSeeking(false)
}, },
} }
},
}
}, []) }, [])
const [isDragging] = useDrag(thumbNode, gestureHandlers) const [isDragging] = useDrag(thumbNode, gestureHandlers)

View file

@ -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. * Called at the end of the drag gesture.
* `dragHappened` will be `true` if the user actually moved the pointer * `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) * (if onDrag isn't called, then this will be false becuase the user hasn't moved the pointer)
*/ */
onDragEnd?: (dragHappened: boolean) => void onDragEnd?: OnDragEndCallback
/** onDrag: OnDragCallback
* This will be called 0 times if the gesture ends up being a click, }
* or 1 or more times if it ends up being a drag gesture.
* // which mouse button to use the drag event
* `dx`: the delta x buttons?:
* `dy`: the delta y | [MouseButton]
* `event`: the mouse event | [MouseButton, MouseButton]
*/ | [MouseButton | MouseButton | MouseButton]
onDrag: (dx: number, dy: number, event: MouseEvent) => void
} }
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'
} }