From 202b61c48c1f0b9f8bb9c1e1b06a8300678e8348 Mon Sep 17 00:00:00 2001 From: Aria Minaei Date: Tue, 10 May 2022 15:30:10 +0200 Subject: [PATCH] Simplify `useDrag()`'s API and implement middle-button dragging in SequenceEditor --- .../src/panels/BasePanel/PanelDragZone.tsx | 72 +++--- .../panels/BasePanel/PanelResizeHandle.tsx | 138 +++++------ .../KeyframeEditor/Connector.tsx | 125 +++++----- .../CurveEditorPopover/CurveSegmentEditor.tsx | 18 +- .../KeyframeEditor/KeyframeDot.tsx | 114 ++++----- .../Right/DopeSheetSelectionView.tsx | 137 ++++++----- .../Right/HorizontallyScrollableArea.tsx | 90 ++++--- .../Right/LengthIndicator/LengthIndicator.tsx | 81 +++---- .../KeyframeEditor/CurveHandle.tsx | 213 +++++++++-------- .../GraphEditorDotNonScalar.tsx | 96 ++++---- .../KeyframeEditor/GraphEditorDotScalar.tsx | 195 ++++++++------- .../FocusRangeZone/FocusRangeStrip.tsx | 116 +++++---- .../FocusRangeZone/FocusRangeThumb.tsx | 136 +++++------ .../FocusRangeZone/FocusRangeZone.tsx | 226 +++++++++--------- .../RightOverlay/MarkerDot.tsx | 70 +++--- .../RightOverlay/Playhead.tsx | 44 ++-- theatre/studio/src/uiComponents/useDrag.ts | 96 ++++++-- 17 files changed, 995 insertions(+), 972 deletions(-) diff --git a/theatre/studio/src/panels/BasePanel/PanelDragZone.tsx b/theatre/studio/src/panels/BasePanel/PanelDragZone.tsx index a24aafa..ee893c8 100644 --- a/theatre/studio/src/panels/BasePanel/PanelDragZone.tsx +++ b/theatre/studio/src/panels/BasePanel/PanelDragZone.tsx @@ -21,52 +21,44 @@ const PanelDragZone: React.FC< const [ref, node] = useRefAndState(null as $IntentionalAny) const dragOpts: Parameters[1] = useMemo(() => { - let stuffBeforeDrag = panelStuffRef.current - let tempTransaction: CommitOrDiscard | undefined - let unlock: VoidFn | undefined return { debugName: 'PanelDragZone', lockCursorTo: 'move', onDragStart() { - stuffBeforeDrag = panelStuffRef.current - if (unlock) { - 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, - }) + const stuffBeforeDrag = panelStuffRef.current + let tempTransaction: CommitOrDiscard | undefined - tempTransaction?.discard() - tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => { - stateEditors.studio.historic.panelPositions.setPanelPosition({ - position, - panelId: stuffBeforeDrag.panelId, - }) - }) - }, - onDragEnd(dragHappened) { - if (unlock) { - const u = unlock - unlock = undefined - u() + const unlock = panelStuff.addBoundsHighlightLock() + + return { + 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() + 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 }, } }, []) diff --git a/theatre/studio/src/panels/BasePanel/PanelResizeHandle.tsx b/theatre/studio/src/panels/BasePanel/PanelResizeHandle.tsx index f31a521..3687c2c 100644 --- a/theatre/studio/src/panels/BasePanel/PanelResizeHandle.tsx +++ b/theatre/studio/src/panels/BasePanel/PanelResizeHandle.tsx @@ -1,10 +1,10 @@ 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 type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore' import useDrag from '@theatre/studio/uiComponents/useDrag' import {lighten} from 'polished' -import React, {useMemo, useRef, useState} from 'react' +import React, {useMemo, useRef} from 'react' import styled from 'styled-components' import {panelDimsToPanelPosition, usePanel} from './BasePanel' import {pointerEventsAutoInNormalMode} from '@theatre/studio/css' @@ -142,96 +142,84 @@ const PanelResizeHandle: React.FC<{ const panelStuff = usePanel() const panelStuffRef = useRef(panelStuff) panelStuffRef.current = panelStuff - const [isDragging, setIsDragging] = useState(false) const [ref, node] = useRefAndState(null as $IntentionalAny) const dragOpts: Parameters[1] = useMemo(() => { - let stuffBeforeDrag = panelStuffRef.current - let tempTransaction: CommitOrDiscard | undefined - let unlock: VoidFn | undefined - return { debugName: 'PanelResizeHandle', lockCursorTo: cursors[which], onDragStart() { - stuffBeforeDrag = panelStuffRef.current - setIsDragging(true) - if (unlock) { - const u = unlock - unlock = undefined - u() - } - unlock = panelStuff.addBoundsHighlightLock() - }, - onDrag(dx, dy) { - const newDims: typeof panelStuff['dims'] = { - ...stuffBeforeDrag.dims, - } + let tempTransaction: CommitOrDiscard | undefined - if (which.startsWith('Bottom')) { - newDims.height = Math.max( - 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 + const stuffBeforeDrag = panelStuffRef.current + const unlock = panelStuff.addBoundsHighlightLock() - newDims.height = height - newDims.top = top - } - 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 + return { + onDrag(dx, dy) { + const newDims: typeof panelStuff['dims'] = { + ...stuffBeforeDrag.dims, + } - newDims.width = width - newDims.left = left - } else if (which.endsWith('Right')) { - newDims.width = Math.max( - newDims.width + dx, - stuffBeforeDrag.minDims.width, - ) - } + if (which.startsWith('Bottom')) { + newDims.height = Math.max( + 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 - const position = panelDimsToPanelPosition(newDims, { - width: window.innerWidth, - height: window.innerHeight, - }) + newDims.height = height + newDims.top = top + } + 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() - tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => { - stateEditors.studio.historic.panelPositions.setPanelPosition({ - position, - panelId: stuffBeforeDrag.panelId, - }) - }) - }, - onDragEnd(dragHappened) { - if (unlock) { - const u = unlock - unlock = undefined - u() + newDims.width = width + newDims.left = left + } else if (which.endsWith('Right')) { + newDims.width = Math.max( + newDims.width + dx, + stuffBeforeDrag.minDims.width, + ) + } + + const position = panelDimsToPanelPosition(newDims, { + width: window.innerWidth, + height: window.innerHeight, + }) + + 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]) - useDrag(node, dragOpts) + const [isDragging] = useDrag(node, dragOpts) const Comp = els[which] const isOnCorner = which.length <= 6 diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/Connector.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/Connector.tsx index ff83b56..d2a61df 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/Connector.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/Connector.tsx @@ -8,13 +8,8 @@ import {lighten} from 'polished' import React from 'react' import {useMemo, useRef} from 'react' import styled from 'styled-components' -import type { - SequenceEditorPanelLayout, - DopeSheetSelection, -} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import {DOT_SIZE_PX} from './KeyframeDot' import type KeyframeEditor from './KeyframeEditor' -import type Sequence from '@theatre/core/sequences/Sequence' import usePopover from '@theatre/studio/uiComponents/Popover/usePopover' import BasicPopover from '@theatre/studio/uiComponents/Popover/BasicPopover' import CurveEditorPopover from './CurveEditorPopover/CurveEditorPopover' @@ -156,84 +151,74 @@ function useDragKeyframe(node: HTMLDivElement | null, props: IProps) { propsRef.current = props const gestureHandlers = useMemo[1]>(() => { - let toUnitSpace: SequenceEditorPanelLayout['scaledSpace']['toUnitSpace'] - let tempTransaction: CommitOrDiscard | undefined - let propsAtStartOfDrag: IProps - let selectionDragHandlers: - | ReturnType - | undefined - let sequence: Sequence return { debugName: 'useDragKeyframe', lockCursorTo: 'ew-resize', onDragStart(event) { const props = propsRef.current + let tempTransaction: CommitOrDiscard | undefined + if (props.selection) { const {selection, leaf} = props const {sheetObject} = leaf - selectionDragHandlers = selection.getDragHandlers({ - ...sheetObject.address, - pathToProp: leaf.pathToProp, - trackId: leaf.trackId, - keyframeId: props.keyframe.id, - domNode: node!, - positionAtStartOfDrag: - props.trackData.keyframes[props.index].position, - }) - selectionDragHandlers.onDragStart?.(event) - return + return selection + .getDragHandlers({ + ...sheetObject.address, + pathToProp: leaf.pathToProp, + trackId: leaf.trackId, + keyframeId: props.keyframe.id, + domNode: node!, + positionAtStartOfDrag: + props.trackData.keyframes[props.index].position, + }) + .onDragStart(event) } - propsAtStartOfDrag = props - sequence = val(propsAtStartOfDrag.layoutP.sheet).getSequence() + const propsAtStartOfDrag = props + const sequence = val(propsAtStartOfDrag.layoutP.sheet).getSequence() - 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) + const toUnitSpace = val( + propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace, + ) - 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 }, } }, []) diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/CurveEditorPopover/CurveSegmentEditor.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/CurveEditorPopover/CurveSegmentEditor.tsx index 5e1b96f..7c3b4ee 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/CurveEditorPopover/CurveSegmentEditor.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/CurveEditorPopover/CurveSegmentEditor.tsx @@ -256,15 +256,17 @@ function useKeyframeDrag( lockCursorTo: 'move', onDragStart() { setFrozen(true) - }, - onDrag(dx, dy) { - if (!svgNode) return + return { + onDrag(dx, dy) { + if (!svgNode) return - props.onCurveChange(setHandles(dx, dy)) - }, - onDragEnd(dragHappened) { - setFrozen(false) - props.onCancelCurveChange() + props.onCurveChange(setHandles(dx, dy)) + }, + onDragEnd(dragHappened) { + setFrozen(false) + props.onCancelCurveChange() + }, + } }, }), [svgNode, props.trackData], diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/KeyframeDot.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/KeyframeDot.tsx index 5279cd3..4bb99b4 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/KeyframeDot.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/KeyframeDot.tsx @@ -1,7 +1,3 @@ -import type { - DopeSheetSelection, - SequenceEditorPanelLayout, -} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import getStudio from '@theatre/studio/getStudio' import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore' import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' @@ -148,14 +144,6 @@ function useDragKeyframe( propsRef.current = props const useDragOpts = useMemo(() => { - let toUnitSpace: SequenceEditorPanelLayout['scaledSpace']['toUnitSpace'] - let tempTransaction: CommitOrDiscard | undefined - let propsAtStartOfDrag: IKeyframeDotProps - - let selectionDragHandlers: - | ReturnType - | undefined - return { debugName: 'KeyframeDot/useDragKeyframe', @@ -164,65 +152,61 @@ function useDragKeyframe( if (props.selection) { const {selection, leaf} = props const {sheetObject} = leaf - selectionDragHandlers = selection.getDragHandlers({ - ...sheetObject.address, - pathToProp: leaf.pathToProp, - trackId: leaf.trackId, - keyframeId: props.keyframe.id, - domNode: node!, - positionAtStartOfDrag: - props.trackData.keyframes[props.index].position, - }) - selectionDragHandlers.onDragStart?.(event) - return + return selection + .getDragHandlers({ + ...sheetObject.address, + pathToProp: leaf.pathToProp, + trackId: leaf.trackId, + keyframeId: props.keyframe.id, + domNode: node!, + positionAtStartOfDrag: + props.trackData.keyframes[props.index].position, + }) + .onDragStart(event) } - propsAtStartOfDrag = props - 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, + const propsAtStartOfDrag = props + const toUnitSpace = val( + propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace, ) - 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 (selectionDragHandlers) { - selectionDragHandlers.onDragEnd?.(dragHappened) + let tempTransaction: CommitOrDiscard | undefined - 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 }, } }, []) diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/DopeSheetSelectionView.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/DopeSheetSelectionView.tsx index 6ac5ea9..4279b63 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/DopeSheetSelectionView.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/DopeSheetSelectionView.tsx @@ -79,27 +79,30 @@ function useCaptureSelection( } 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)( - posInScaledSpace, - ) + const posInScaledSpace = event.clientX - rect.left - ref.current = { - positions: [ref.current!.positions[0], posInUnitSpace], - ys: [ref.current!.ys[0], event.clientY - rect.top], + const posInUnitSpace = val(layoutP.scaledSpace.toUnitSpace)( + posInScaledSpace, + ) + + 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]), @@ -183,59 +186,65 @@ namespace utils { type: 'DopeSheetSelection', byObjectKey: {}, getDragHandlers(origin) { - let tempTransaction: CommitOrDiscard | undefined - - let toUnitSpace: SequenceEditorPanelLayout['scaledSpace']['toUnitSpace'] return { debugName: 'DopeSheetSelectionView/boundsToSelection', onDragStart() { - toUnitSpace = val(layoutP.scaledSpace.toUnitSpace) - }, - onDrag(dx, _, event) { - if (tempTransaction) { - tempTransaction.discard() - tempTransaction = undefined - } + let tempTransaction: CommitOrDiscard | undefined - const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, { - ignore: origin.domNode, - }) + const toUnitSpace = val(layoutP.scaledSpace.toUnitSpace) - let delta: number - 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, - }) + return { + onDrag(dx, _, event) { + if (tempTransaction) { + tempTransaction.discard() + tempTransaction = undefined } - } - }) - }, - onDragEnd(dragHappened) { - if (dragHappened) tempTransaction?.commit() - else tempTransaction?.discard() - tempTransaction = undefined + + const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, { + ignore: origin.domNode, + }) + + let delta: number + 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() + }, + } }, } }, diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/HorizontallyScrollableArea.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/HorizontallyScrollableArea.tsx index c6b38d0..66981cc 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/HorizontallyScrollableArea.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/HorizontallyScrollableArea.tsx @@ -1,4 +1,3 @@ -import type Sequence from '@theatre/core/sequences/Sequence' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import useDrag from '@theatre/studio/uiComponents/useDrag' import useRefAndState from '@theatre/studio/utils/useRefAndState' @@ -50,7 +49,7 @@ const HorizontallyScrollableArea: React.FC<{ ) useHandlePanAndZoom(layoutP, containerNode) - useDragHandlers(layoutP, containerNode) + useDragPlayheadHandlers(layoutP, containerNode) useUpdateScrollFromClippedSpaceRange(layoutP, containerNode) return ( @@ -70,31 +69,13 @@ const HorizontallyScrollableArea: React.FC<{ export default HorizontallyScrollableArea -function useDragHandlers( +function useDragPlayheadHandlers( layoutP: Pointer, containerEl: HTMLDivElement | null, ) { const handlers = useMemo((): Parameters[1] => { - let posBeforeSeek = 0 - let sequence: Sequence - let scaledSpaceToUnitSpace: typeof layoutP.scaledSpace.toUnitSpace.$$__pointer_type - const setIsSeeking = val(layoutP.seeker.setIsSeeking) - return { 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) { if (event.target instanceof HTMLInputElement) { // editing some value @@ -124,16 +105,38 @@ function useDragHandlers( Infinity, ) - sequence = val(layoutP.sheet).getSequence() + const setIsSeeking = val(layoutP.seeker.setIsSeeking) + + const sequence = val(layoutP.sheet).getSequence() sequence.position = initialPositionInUnitSpace - posBeforeSeek = initialPositionInUnitSpace - scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace) + const posBeforeSeek = initialPositionInUnitSpace + const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace) setIsSeeking(true) - }, - onDragEnd() { - setIsSeeking(false) + + 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() { + setIsSeeking(false) + }, + } }, } }, [layoutP, containerEl]) @@ -233,6 +236,39 @@ function useHandlePanAndZoom( node.removeEventListener('wheel', receiveWheelEvent, listenerOptions) } }, [node, layoutP]) + + useDrag( + node, + useMemo[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]) { diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/LengthIndicator/LengthIndicator.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/LengthIndicator/LengthIndicator.tsx index b0a6236..b92d296 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/LengthIndicator/LengthIndicator.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/LengthIndicator/LengthIndicator.tsx @@ -1,7 +1,7 @@ import {usePrism} from '@theatre/react' import type {Pointer} 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 type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' 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 useDrag from '@theatre/studio/uiComponents/useDrag' import getStudio from '@theatre/studio/getStudio' -import type Sheet from '@theatre/core/sheets/Sheet' import usePopover from '@theatre/studio/uiComponents/Popover/usePopover' import { includeLockFrameStampAttrs, @@ -219,58 +218,56 @@ function useDragBulge( ): [isDragging: boolean] { const propsRef = useRef(props) propsRef.current = props - const [isDragging, setIsDragging] = useState(false) - - useLockFrameStampPosition(isDragging, -1) const gestureHandlers = useMemo[1]>(() => { - let toUnitSpace: SequenceEditorPanelLayout['scaledSpace']['toUnitSpace'] - let tempTransaction: CommitOrDiscard | undefined - let propsAtStartOfDrag: IProps - let sheet: Sheet - let initialLength: number - return { debugName: 'LengthIndicator/useDragBulge', lockCursorTo: 'ew-resize', onDragStart(event) { - setIsDragging(true) - propsAtStartOfDrag = propsRef.current - sheet = val(propsRef.current.layoutP.sheet) - initialLength = sheet.getSequence().length + let tempTransaction: CommitOrDiscard | undefined - toUnitSpace = val(propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace) - }, - onDrag(dx, dy, event) { - const delta = toUnitSpace(dx) - if (tempTransaction) { - tempTransaction.discard() - tempTransaction = undefined + 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) { + 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] } diff --git a/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/CurveHandle.tsx b/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/CurveHandle.tsx index fafd827..09652ae 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/CurveHandle.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/CurveHandle.tsx @@ -1,10 +1,8 @@ -import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import getStudio from '@theatre/studio/getStudio' import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore' import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' import useDrag from '@theatre/studio/uiComponents/useDrag' import useRefAndState from '@theatre/studio/utils/useRefAndState' -import type {VoidFn} from '@theatre/shared/utils/types' import {val} from '@theatre/dataverse' import {clamp} from 'lodash-es' import React, {useMemo, useRef} from 'react' @@ -125,115 +123,130 @@ function useOurDrags(node: SVGCircleElement | null, props: IProps): void { propsRef.current = props const handlers = useMemo[1]>(() => { - let scaledToUnitSpace: SequenceEditorPanelLayout['scaledSpace']['toUnitSpace'] - let verticalToExtremumSpace: SequenceEditorPanelLayout['graphEditorVerticalSpace']['toExtremumSpace'] - let propsAtStartOfDrag: IProps - let tempTransaction: CommitOrDiscard | undefined - let unlockExtremums: VoidFn | undefined return { debugName: 'CurveHandler/useOurDrags', lockCursorTo: 'move', onDragStart() { - propsAtStartOfDrag = propsRef.current + let tempTransaction: CommitOrDiscard | undefined - scaledToUnitSpace = val( + const propsAtStartOfDrag = propsRef.current + + const scaledToUnitSpace = val( propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace, ) - verticalToExtremumSpace = val( + const verticalToExtremumSpace = val( propsAtStartOfDrag.layoutP.graphEditorVerticalSpace.toExtremumSpace, ) - unlockExtremums = propsAtStartOfDrag.extremumSpace.lock() - }, - onDrag(dxInScaledSpace, dy) { - if (tempTransaction) { - tempTransaction.discard() - tempTransaction = undefined + const unlockExtremums = propsAtStartOfDrag.extremumSpace.lock() + + return { + onDrag(dxInScaledSpace, dy) { + if (tempTransaction) { + 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 }, } }, []) diff --git a/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/GraphEditorDotNonScalar.tsx b/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/GraphEditorDotNonScalar.tsx index aabeb9b..5ffdbe8 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/GraphEditorDotNonScalar.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/GraphEditorDotNonScalar.tsx @@ -1,10 +1,8 @@ -import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import getStudio from '@theatre/studio/getStudio' import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore' import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' import useDrag from '@theatre/studio/uiComponents/useDrag' import useRefAndState from '@theatre/studio/utils/useRefAndState' -import type {VoidFn} from '@theatre/shared/utils/types' import {val} from '@theatre/dataverse' import React, {useMemo, useRef, useState} from 'react' import styled from 'styled-components' @@ -107,68 +105,62 @@ function useDragKeyframe( propsRef.current = _props const gestureHandlers = useMemo[1]>(() => { - let toUnitSpace: SequenceEditorPanelLayout['scaledSpace']['toUnitSpace'] - - let propsAtStartOfDrag: IProps - let tempTransaction: CommitOrDiscard | undefined - let unlockExtremums: VoidFn | undefined - return { debugName: 'GraphEditorDotNonScalar/useDragKeyframe', lockCursorTo: 'ew-resize', onDragStart(event) { 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() - }, - onDrag(dx, dy) { - const original = - propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index] + return { + onDrag(dx, dy) { + const original = + propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index] - const deltaPos = toUnitSpace(dx) + const deltaPos = toUnitSpace(dx) - const updatedKeyframes: Keyframe[] = [] + const updatedKeyframes: Keyframe[] = [] - const cur: Keyframe = { - ...original, - position: original.position + deltaPos, - value: original.value, - handles: [...original.handles], + const cur: Keyframe = { + ...original, + position: original.position + deltaPos, + value: original.value, + 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 }, } }, []) diff --git a/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/GraphEditorDotScalar.tsx b/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/GraphEditorDotScalar.tsx index 9012f56..996376e 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/GraphEditorDotScalar.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/GraphEditorDotScalar.tsx @@ -1,10 +1,8 @@ -import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import getStudio from '@theatre/studio/getStudio' import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore' import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu' import useDrag from '@theatre/studio/uiComponents/useDrag' import useRefAndState from '@theatre/studio/utils/useRefAndState' -import type {VoidFn} from '@theatre/shared/utils/types' import {val} from '@theatre/dataverse' import React, {useMemo, useRef, useState} from 'react' import styled from 'styled-components' @@ -108,121 +106,122 @@ function useDragKeyframe( propsRef.current = _props const gestureHandlers = useMemo[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 { debugName: 'GraphEditorDotScalar/useDragKeyframe', lockCursorTo: 'move', onDragStart(event) { setIsDragging(true) - keepSpeeds = !!event.altKey + const keepSpeeds = !!event.altKey - propsAtStartOfDrag = propsRef.current + const propsAtStartOfDrag = propsRef.current - toUnitSpace = val(propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace) - verticalToExtremumSpace = val( + const toUnitSpace = val( + propsAtStartOfDrag.layoutP.scaledSpace.toUnitSpace, + ) + const verticalToExtremumSpace = val( propsAtStartOfDrag.layoutP.graphEditorVerticalSpace.toExtremumSpace, ) - unlockExtremums = propsAtStartOfDrag.extremumSpace.lock() - }, - onDrag(dx, dy) { - const original = - propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index] + const unlockExtremums = propsAtStartOfDrag.extremumSpace.lock() + let tempTransaction: CommitOrDiscard | undefined - const deltaPos = toUnitSpace(dx) - const dyInVerticalSpace = -dy - const dYInExtremumSpace = verticalToExtremumSpace(dyInVerticalSpace) + return { + onDrag(dx, dy) { + const original = + propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index] - const dYInValueSpace = - propsAtStartOfDrag.extremumSpace.deltaToValueSpace(dYInExtremumSpace) + const deltaPos = toUnitSpace(dx) + const dyInVerticalSpace = -dy + const dYInExtremumSpace = verticalToExtremumSpace(dyInVerticalSpace) - const updatedKeyframes: Keyframe[] = [] + const dYInValueSpace = + propsAtStartOfDrag.extremumSpace.deltaToValueSpace( + dYInExtremumSpace, + ) - const cur: Keyframe = { - ...original, - position: original.position + deltaPos, - value: (original.value as number) + dYInValueSpace, - handles: [...original.handles], - } + const updatedKeyframes: Keyframe[] = [] - updatedKeyframes.push(cur) - - if (keepSpeeds) { - const prev = - propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index - 1] - - if ( - prev && - Math.abs((original.value as number) - (prev.value as number)) > 0 - ) { - const newPrev: Keyframe = { - ...prev, - handles: [...prev.handles], + const cur: Keyframe = { + ...original, + position: original.position + deltaPos, + value: (original.value as number) + dYInValueSpace, + handles: [...original.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(cur) + + if (keepSpeeds) { + const prev = + propsAtStartOfDrag.trackData.keyframes[ + propsAtStartOfDrag.index - 1 + ] + + 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 = 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() + 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() + } + }, } - if (dragHappened) { - tempTransaction?.commit() - } else { - tempTransaction?.discard() - } - tempTransaction = undefined }, } }, []) diff --git a/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/FocusRangeZone/FocusRangeStrip.tsx b/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/FocusRangeZone/FocusRangeStrip.tsx index b8ae20a..9276419 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/FocusRangeZone/FocusRangeStrip.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/FocusRangeZone/FocusRangeStrip.tsx @@ -1,7 +1,7 @@ import type {Pointer} from '@theatre/dataverse' import {prism, val} from '@theatre/dataverse' 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 getStudio from '@theatre/studio/getStudio' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' @@ -179,81 +179,73 @@ const FocusRangeStrip: React.FC<{ const sheet = useVal(layoutP.sheet) const gestureHandlers = useMemo((): Parameters[1] => { - let sequence = sheet.getSequence() - let startPosBeforeDrag: number, - endPosBeforeDrag: number, - tempTransaction: CommitOrDiscard | undefined - let dragHappened = false - let existingRange: {enabled: boolean; range: IRange} | undefined - let target: HTMLDivElement | undefined let newStartPosition: number, newEndPosition: number return { debugName: 'FocusRangeStrip', onDragStart(event) { - existingRange = existingRangeD.getValue() + let tempTransaction: CommitOrDiscard | undefined + let existingRange = existingRangeD.getValue() + if (!existingRange) return false - if (existingRange) { - startPosBeforeDrag = existingRange.range.start - endPosBeforeDrag = existingRange.range.end - dragHappened = false - sequence = val(layoutP.sheet).getSequence() - 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 startPosBeforeDrag = existingRange.range.start + const endPosBeforeDrag = existingRange.range.end + let dragHappened = false + const sequence = val(layoutP.sheet).getSequence() + isDraggingRef.current = true - const start = startPosBeforeDrag + deltaPos - let end = endPosBeforeDrag + deltaPos + return { + onDrag(dx) { + existingRange = existingRangeD.getValue() + if (existingRange) { + dragHappened = true + const deltaPos = scaledSpaceToUnitSpace(dx) - if (end < start) { - end = start - } + const start = startPosBeforeDrag + deltaPos + let end = endPosBeforeDrag + deltaPos - ;[newStartPosition, newEndPosition] = clampRange( - [start, end], - [0, sequence.length], - ).map((pos) => sequence.closestGridPosition(pos)) + if (end < start) { + end = start + } - if (tempTransaction) { - tempTransaction.discard() - } + ;[newStartPosition, newEndPosition] = clampRange( + [start, end], + [0, sequence.length], + ).map((pos) => sequence.closestGridPosition(pos)) - tempTransaction = getStudio().tempTransaction(({stateEditors}) => { - stateEditors.studio.ahistoric.projects.stateByProjectId.stateBySheetId.sequence.focusRange.set( - { - ...sheet.address, - range: { - start: newStartPosition, - end: newEndPosition, + if (tempTransaction) { + tempTransaction.discard() + } + + tempTransaction = getStudio().tempTransaction( + ({stateEditors}) => { + 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) { - if (dragHappened && tempTransaction !== undefined) { - tempTransaction.commit() - } else if (tempTransaction) { - tempTransaction.discard() - } - tempTransaction = undefined - } - if (target !== undefined) { - target = undefined + ) + } + }, + onDragEnd() { + isDraggingRef.current = false + if (existingRange) { + if (dragHappened && tempTransaction !== undefined) { + tempTransaction.commit() + } else if (tempTransaction) { + tempTransaction.discard() + } + } + }, } }, + lockCursorTo: 'grabbing', } }, [sheet, scaledSpaceToUnitSpace]) diff --git a/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/FocusRangeZone/FocusRangeThumb.tsx b/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/FocusRangeZone/FocusRangeThumb.tsx index 93888a2..607c5ac 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/FocusRangeZone/FocusRangeThumb.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/FocusRangeZone/FocusRangeThumb.tsx @@ -22,7 +22,6 @@ import { useLockFrameStampPosition, } from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider' import {focusRangeStripTheme, RangeStrip} from './FocusRangeStrip' -import type Sheet from '@theatre/core/sheets/Sheet' import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap' const TheDiv = styled.div<{enabled: boolean; type: 'start' | 'end'}>` @@ -172,87 +171,84 @@ const FocusRangeThumb: React.FC<{ ) const gestureHandlers = useMemo((): Parameters[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 { debugName: 'FocusRangeThumb', onDragStart() { - sheet = val(layoutP.sheet) + let tempTransaction: CommitOrDiscard | undefined + let range: IRange + + const sheet = val(layoutP.sheet) const sequence = sheet.getSequence() - defaultRange = {start: 0, end: sequence.length} + const defaultRange = {start: 0, end: sequence.length} let existingRange = existingRangeD.getValue() || { range: defaultRange, enabled: false, } - focusRangeEnabled = existingRange.enabled + const focusRangeEnabled = existingRange.enabled - posBeforeDrag = existingRange.range[thumbType] - scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace) - minFocusRangeStripWidth = scaledSpaceToUnitSpace( + const posBeforeDrag = existingRange.range[thumbType] + const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace) + const minFocusRangeStripWidth = scaledSpaceToUnitSpace( focusRangeStripTheme.rangeStripMinWidth, ) - }, - onDrag(dx, _, event) { - let newPosition: number - const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, { - ignore: hitZoneNode, - }) - if (snapPos != null) { - newPosition = snapPos + + return { + onDrag(dx, _, event) { + let newPosition: number + const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, { + ignore: hitZoneNode, + }) + 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]) diff --git a/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/FocusRangeZone/FocusRangeZone.tsx b/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/FocusRangeZone/FocusRangeZone.tsx index 5287045..ab9197b 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/FocusRangeZone/FocusRangeZone.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/FocusRangeZone/FocusRangeZone.tsx @@ -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 {prism, val} from '@theatre/dataverse' 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 { panelDimsToPanelPosition, @@ -102,152 +100,150 @@ function usePanelDragZoneGestureHandlers( const focusRangeCreationGestureHandlers = (): Parameters< typeof useDrag >[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 { debugName: 'FocusRangeZone/focusRangeCreationGestureHandlers', onDragStart(event) { - clippedSpaceToUnitSpace = val(layoutP.clippedSpace.toUnitSpace) - scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace) - sheet = val(layoutP.sheet) - sequence = sheet.getSequence() + let tempTransaction: CommitOrDiscard | undefined + + const clippedSpaceToUnitSpace = val(layoutP.clippedSpace.toUnitSpace) + const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace) + const sheet = val(layoutP.sheet) + const sequence = sheet.getSequence() const targetElement: HTMLElement = event.target as HTMLElement const rect = targetElement!.getBoundingClientRect() - startPosInUnitSpace = clippedSpaceToUnitSpace( + const startPosInUnitSpace = clippedSpaceToUnitSpace( event.clientX - rect.left, ) - minFocusRangeStripWidth = scaledSpaceToUnitSpace( + const minFocusRangeStripWidth = scaledSpaceToUnitSpace( focusRangeStripTheme.rangeStripMinWidth, ) - }, - onDrag(dx) { - const deltaPos = scaledSpaceToUnitSpace(dx) - let start = startPosInUnitSpace - let end = startPosInUnitSpace + deltaPos + return { + onDrag(dx) { + const deltaPos = scaledSpaceToUnitSpace(dx) - ;[start, end] = [ - clamp(start, 0, sequence.length), - clamp(end, 0, sequence.length), - ].map((pos) => sequence.closestGridPosition(pos)) + let start = startPosInUnitSpace + let end = startPosInUnitSpace + deltaPos - if (end < start) { - ;[start, end] = [ - Math.max(Math.min(end, start - minFocusRangeStripWidth), 0), - start, - ] - } else if (dx > 0) { - end = Math.min( - Math.max(end, start + minFocusRangeStripWidth), - sequence.length, - ) + ;[start, end] = [ + clamp(start, 0, sequence.length), + clamp(end, 0, sequence.length), + ].map((pos) => sequence.closestGridPosition(pos)) + + if (end < start) { + ;[start, end] = [ + Math.max(Math.min(end, start - minFocusRangeStripWidth), 0), + 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', } } const panelMoveGestureHandlers = (): Parameters[1] => { - let stuffBeforeDrag = panelStuffRef.current - let tempTransaction: CommitOrDiscard | undefined - let unlock: VoidFn | undefined return { debugName: 'FocusRangeZone/panelMoveGestureHandlers', onDragStart() { - stuffBeforeDrag = panelStuffRef.current - if (unlock) { - 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, - }) + let tempTransaction: CommitOrDiscard | undefined + const stuffBeforeDrag = panelStuffRef.current - tempTransaction?.discard() - tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => { - stateEditors.studio.historic.panelPositions.setPanelPosition({ - position, - panelId: stuffBeforeDrag.panelId, - }) - }) - }, - onDragEnd(dragHappened) { - if (unlock) { - const u = unlock - unlock = undefined - u() + const unlock = panelStuffRef.current.addBoundsHighlightLock() + + return { + 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() + 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', } } - let currentGestureHandlers: undefined | Parameters[1] - return { debugName: 'FocusRangeZone', onDragStart(event) { - if (event.shiftKey) { - setMode('creating') - currentGestureHandlers = focusRangeCreationGestureHandlers() - } else { - setMode('moving-panel') - currentGestureHandlers = panelMoveGestureHandlers() + const [_mode, currentGestureHandlers] = event.shiftKey + ? [ + 'creating' as 'creating', + focusRangeCreationGestureHandlers().onDragStart(event), + ] + : [ + '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]) diff --git a/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/MarkerDot.tsx b/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/MarkerDot.tsx index d62eaaa..3421607 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/MarkerDot.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/MarkerDot.tsx @@ -247,46 +247,44 @@ function useDragMarker( propsRef.current = props const useDragOpts = useMemo(() => { - let toUnitSpace: SequenceEditorPanelLayout['scaledSpace']['toUnitSpace'] - let tempTransaction: CommitOrDiscard | undefined - let markerAtStartOfDrag: StudioHistoricStateSequenceEditorMarker - return { debugName: `MarkerDot/useDragMarker (${props.marker.id})`, - onDragStart(_event) { - markerAtStartOfDrag = propsRef.current.marker - toUnitSpace = val(props.layoutP.scaledSpace.toUnitSpace) - }, - 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, - ) + const markerAtStartOfDrag = propsRef.current.marker + const toUnitSpace = val(props.layoutP.scaledSpace.toUnitSpace) + let tempTransaction: CommitOrDiscard | undefined - tempTransaction?.discard() - 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() - tempTransaction = undefined + return { + 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() + 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() + }, + } }, } }, []) diff --git a/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/Playhead.tsx b/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/Playhead.tsx index 94017cd..9f898b5 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/Playhead.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/RightOverlay/Playhead.tsx @@ -1,4 +1,3 @@ -import type Sequence from '@theatre/core/sequences/Sequence' import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout' import RoomToClick from '@theatre/studio/uiComponents/RoomToClick' import useDrag from '@theatre/studio/uiComponents/useDrag' @@ -203,32 +202,31 @@ const Playhead: React.FC<{layoutP: Pointer}> = ({ ) const gestureHandlers = useMemo((): Parameters[1] => { - const setIsSeeking = val(layoutP.seeker.setIsSeeking) - - let posBeforeSeek = 0 - let sequence: Sequence - let scaledSpaceToUnitSpace: typeof layoutP.scaledSpace.toUnitSpace.$$__pointer_type - return { debugName: 'Playhead', onDragStart() { - sequence = val(layoutP.sheet).getSequence() - posBeforeSeek = sequence.position - scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace) - setIsSeeking(true) - }, - onDrag(dx, _, event) { - const deltaPos = scaledSpaceToUnitSpace(dx) + const setIsSeeking = val(layoutP.seeker.setIsSeeking) - sequence.position = - DopeSnap.checkIfMouseEventSnapToPos(event, { - ignore: thumbNode, - }) ?? - // unsnapped - clamp(posBeforeSeek + deltaPos, 0, sequence.length) - }, - onDragEnd() { - setIsSeeking(false) + const sequence = val(layoutP.sheet).getSequence() + const posBeforeSeek = sequence.position + const scaledSpaceToUnitSpace = val(layoutP.scaledSpace.toUnitSpace) + setIsSeeking(true) + + return { + onDrag(dx, _, event) { + const deltaPos = scaledSpaceToUnitSpace(dx) + + sequence.position = + DopeSnap.checkIfMouseEventSnapToPos(event, { + ignore: thumbNode, + }) ?? + // unsnapped + clamp(posBeforeSeek + deltaPos, 0, sequence.length) + }, + onDragEnd() { + setIsSeeking(false) + }, + } }, } }, []) diff --git a/theatre/studio/src/uiComponents/useDrag.ts b/theatre/studio/src/uiComponents/useDrag.ts index 01be584..e9fc258 100644 --- a/theatre/studio/src/uiComponents/useDrag.ts +++ b/theatre/studio/src/uiComponents/useDrag.ts @@ -4,6 +4,28 @@ import useRefAndState from '@theatre/studio/utils/useRefAndState' import {useCssCursorLock} from './PointerEventsHandler' import type {CapturedPointer} 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 = { /** @@ -35,22 +57,23 @@ export type UseDragOpts = { * 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 */ - onDragStart?: (event: MouseEvent) => void | 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) - */ - onDragEnd?: (dragHappened: boolean) => void - /** - * 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. - * - * `dx`: the delta x - * `dy`: the delta y - * `event`: the mouse event - */ - onDrag: (dx: number, dy: number, event: MouseEvent) => void + 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) + */ + onDragEnd?: OnDragEndCallback + onDrag: OnDragCallback + } + + // which mouse button to use the drag event + buttons?: + | [MouseButton] + | [MouseButton, MouseButton] + | [MouseButton | MouseButton | MouseButton] } export default function useDrag( @@ -80,9 +103,15 @@ export default function useDrag( } }>({dragHappened: false, startPos: {x: 0, y: 0}}) + const callbacksRef = useRef<{ + onDrag: OnDragCallback + onDragEnd: OnDragEndCallback + }>({onDrag: noop, onDragEnd: noop}) + const capturedPointerRef = useRef() useLayoutEffect(() => { if (!target) return + let lastDeltas = [0, 0] const getDistances = (event: MouseEvent): [number, number] => { const {startPos} = stateRef.current @@ -94,14 +123,26 @@ export default function useDrag( modeRef.current = 'dragging' 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 = () => { removeDragListeners() modeRef.current = 'notDragging' - optsRef.current.onDragEnd?.(stateRef.current.dragHappened) + callbacksRef.current.onDragEnd(stateRef.current.dragHappened) } const addDragListeners = () => { @@ -136,13 +177,18 @@ export default function useDrag( const opts = optsRef.current 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, - // but when defined, we can allow the handler to return false to indicate ignore this dragging - if (opts.onDragStart != null) { - const shouldIgnore = opts.onDragStart(event) === false - if (shouldIgnore) return + if (!acceptedButtons.includes(event.button)) return + + const returnOfOnDragStart = opts.onDragStart(event) + + 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 @@ -175,7 +221,7 @@ export default function useDrag( target.removeEventListener('click', preventUnwantedClick as $FixMe) if (modeRef.current !== 'notDragging') { - optsRef.current.onDragEnd?.(modeRef.current === 'dragging') + callbacksRef.current.onDragEnd?.(modeRef.current === 'dragging') } modeRef.current = 'notDragging' }