Refactor data-pos common code
This commit is contained in:
parent
030b6d2804
commit
d4060730d7
15 changed files with 148 additions and 192 deletions
|
@ -14,7 +14,7 @@ import {lighten} from 'polished'
|
|||
import React, {useMemo, useRef} from 'react'
|
||||
import styled from 'styled-components'
|
||||
import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {attributeNameThatLocksFramestamp} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {includeLockFrameStampAttrs} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {
|
||||
lockedCursorCssVarName,
|
||||
useCssCursorLock,
|
||||
|
@ -22,6 +22,7 @@ import {
|
|||
import SnapCursor from './SnapCursor.svg'
|
||||
import selectedKeyframeIdsIfInSingleTrack from '@theatre/studio/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/selectedKeyframeIdsIfInSingleTrack'
|
||||
import type {IKeyframeEditorProps} from './KeyframeEditor'
|
||||
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
|
||||
|
||||
export const DOT_SIZE_PX = 6
|
||||
const HIT_ZONE_SIZE_PX = 12
|
||||
|
@ -106,11 +107,8 @@ const KeyframeDot: React.VFC<IKeyframeDotProps> = (props) => {
|
|||
<>
|
||||
<HitZone
|
||||
ref={ref}
|
||||
data-pos={props.keyframe.position.toFixed(3)}
|
||||
{...{
|
||||
[attributeNameThatLocksFramestamp]:
|
||||
props.keyframe.position.toFixed(3),
|
||||
}}
|
||||
{...includeLockFrameStampAttrs(props.keyframe.position)}
|
||||
{...DopeSnap.includePositionSnapAttrs(props.keyframe.position)}
|
||||
className={isDragging ? 'beingDragged' : ''}
|
||||
/>
|
||||
<Diamond isSelected={!!props.selection} />
|
||||
|
@ -190,31 +188,19 @@ function useDragKeyframe(
|
|||
|
||||
const original =
|
||||
propsAtStartOfDrag.trackData.keyframes[propsAtStartOfDrag.index]
|
||||
const deltaPos = toUnitSpace(dx)
|
||||
const newPosBeforeSnapping = Math.max(original.position + deltaPos, 0)
|
||||
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,
|
||||
)
|
||||
|
||||
let newPosition = newPosBeforeSnapping
|
||||
|
||||
const snapTarget = event
|
||||
.composedPath()
|
||||
.find(
|
||||
(el): el is Element =>
|
||||
el instanceof Element &&
|
||||
el !== node &&
|
||||
el.hasAttribute('data-pos'),
|
||||
)
|
||||
|
||||
if (snapTarget) {
|
||||
const snapPos = parseFloat(snapTarget.getAttribute('data-pos')!)
|
||||
if (isFinite(snapPos)) {
|
||||
newPosition = snapPos
|
||||
}
|
||||
}
|
||||
|
||||
if (tempTransaction) {
|
||||
tempTransaction.discard()
|
||||
tempTransaction = undefined
|
||||
}
|
||||
tempTransaction?.discard()
|
||||
tempTransaction = undefined
|
||||
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
||||
stateEditors.coreByProject.historic.sheetsById.sequence.replaceKeyframes(
|
||||
{
|
||||
|
|
|
@ -16,6 +16,7 @@ import type {
|
|||
SequenceEditorPanelLayout,
|
||||
} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||
import type {SequenceEditorTree_AllRowTypes} from '@theatre/studio/panels/SequenceEditorPanel/layout/tree'
|
||||
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
|
||||
|
||||
const Container = styled.div<{isShiftDown: boolean}>`
|
||||
cursor: ${(props) => (props.isShiftDown ? 'cell' : 'default')};
|
||||
|
@ -191,26 +192,20 @@ namespace utils {
|
|||
toUnitSpace = val(layoutP.scaledSpace.toUnitSpace)
|
||||
},
|
||||
onDrag(dx, _, event) {
|
||||
let delta = toUnitSpace(dx)
|
||||
if (tempTransaction) {
|
||||
tempTransaction.discard()
|
||||
tempTransaction = undefined
|
||||
}
|
||||
|
||||
const snapTarget = event
|
||||
.composedPath()
|
||||
.find(
|
||||
(el): el is Element =>
|
||||
el instanceof Element &&
|
||||
el !== origin.domNode &&
|
||||
el.hasAttribute('data-pos'),
|
||||
)
|
||||
const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, {
|
||||
ignore: origin.domNode,
|
||||
})
|
||||
|
||||
if (snapTarget) {
|
||||
const snapPos = parseFloat(snapTarget.getAttribute('data-pos')!)
|
||||
if (isFinite(snapPos)) {
|
||||
delta = snapPos - origin.positionAtStartOfDrag
|
||||
}
|
||||
let delta: number
|
||||
if (snapPos != null) {
|
||||
delta = snapPos - origin.positionAtStartOfDrag
|
||||
} else {
|
||||
delta = toUnitSpace(dx)
|
||||
}
|
||||
|
||||
tempTransaction = getStudio()!.tempTransaction(({stateEditors}) => {
|
||||
|
@ -238,15 +233,8 @@ namespace utils {
|
|||
})
|
||||
},
|
||||
onDragEnd(dragHappened) {
|
||||
if (dragHappened) {
|
||||
if (tempTransaction) {
|
||||
tempTransaction.commit()
|
||||
}
|
||||
} else {
|
||||
if (tempTransaction) {
|
||||
tempTransaction.discard()
|
||||
}
|
||||
}
|
||||
if (dragHappened) tempTransaction?.commit()
|
||||
else tempTransaction?.discard()
|
||||
tempTransaction = undefined
|
||||
},
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import {useReceiveVerticalWheelEvent} from '@theatre/studio/panels/SequenceEdito
|
|||
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||
import {useCssCursorLock} from '@theatre/studio/uiComponents/PointerEventsHandler'
|
||||
import type {IRange} from '@theatre/shared/utils/types'
|
||||
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
|
||||
|
||||
const Container = styled.div`
|
||||
position: absolute;
|
||||
|
@ -87,19 +88,9 @@ function useDragHandlers(
|
|||
|
||||
let newPosition = unsnappedPos
|
||||
|
||||
const snapTarget = event.composedPath().find(
|
||||
(el): el is Element =>
|
||||
el instanceof Element &&
|
||||
// el !== thumbNode &&
|
||||
el.hasAttribute('data-pos'),
|
||||
)
|
||||
|
||||
if (snapTarget) {
|
||||
const snapPos = parseFloat(snapTarget.getAttribute('data-pos')!)
|
||||
|
||||
if (isFinite(snapPos)) {
|
||||
newPosition = snapPos
|
||||
}
|
||||
const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, {})
|
||||
if (snapPos != null) {
|
||||
newPosition = snapPos
|
||||
}
|
||||
|
||||
sequence.position = newPosition
|
||||
|
|
|
@ -13,7 +13,7 @@ import getStudio from '@theatre/studio/getStudio'
|
|||
import type Sheet from '@theatre/core/sheets/Sheet'
|
||||
import usePopover from '@theatre/studio/uiComponents/Popover/usePopover'
|
||||
import {
|
||||
attributeNameThatLocksFramestamp,
|
||||
includeLockFrameStampAttrs,
|
||||
useLockFrameStampPosition,
|
||||
} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {GoChevronLeft, GoChevronRight} from 'react-icons/all'
|
||||
|
@ -130,6 +130,10 @@ type IProps = {
|
|||
layoutP: Pointer<SequenceEditorPanelLayout>
|
||||
}
|
||||
|
||||
/**
|
||||
* This appears at the end of the sequence where you can adjust the length of the sequence.
|
||||
* Kinda looks like `< >` at the top bar at end of the sequence editor.
|
||||
*/
|
||||
const LengthIndicator: React.FC<IProps> = ({layoutP}) => {
|
||||
const [nodeRef, node] = useRefAndState<HTMLDivElement | null>(null)
|
||||
const [isDragging] = useDragBulge(node, {layoutP})
|
||||
|
@ -186,7 +190,7 @@ const LengthIndicator: React.FC<IProps> = ({layoutP}) => {
|
|||
onClick={(e) => {
|
||||
openPopover(e, node!)
|
||||
}}
|
||||
{...{[attributeNameThatLocksFramestamp]: 'hide'}}
|
||||
{...includeLockFrameStampAttrs('hide')}
|
||||
>
|
||||
<GoChevronLeft />
|
||||
<GoChevronRight />
|
||||
|
|
|
@ -147,6 +147,14 @@ export const useLockFrameStampPosition = (shouldLock: boolean, val: number) => {
|
|||
* This attribute is used so that when the cursor hovers over a keyframe,
|
||||
* the framestamp snaps to the position of that keyframe.
|
||||
*
|
||||
* Use as a spread in a React element.
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <div {...includeLockFrameStampAttrs(10)}/>
|
||||
* ```
|
||||
*
|
||||
* @remarks
|
||||
* Elements that need this behavior must set a data attribute like so:
|
||||
* <div data-theatre-lock-framestamp-to="120.55" />
|
||||
* Setting this attribute to "hide" hides the stamp.
|
||||
|
@ -157,9 +165,13 @@ export const useLockFrameStampPosition = (shouldLock: boolean, val: number) => {
|
|||
* `pointer-events` on an element that should lock the framestamp.
|
||||
*
|
||||
* See {@link FrameStampPositionProvider}
|
||||
*
|
||||
*/
|
||||
export const attributeNameThatLocksFramestamp =
|
||||
'data-theatre-lock-framestamp-to'
|
||||
export const includeLockFrameStampAttrs = (value: number | 'hide') => ({
|
||||
[ATTR_LOCK_FRAMESTAMP]: value === 'hide' ? value : value.toFixed(3),
|
||||
})
|
||||
|
||||
const ATTR_LOCK_FRAMESTAMP = 'data-theatre-lock-framestamp-to'
|
||||
|
||||
const pointerPositionInUnitSpace = (
|
||||
layoutP: Pointer<SequenceEditorPanelLayout>,
|
||||
|
@ -175,8 +187,8 @@ const pointerPositionInUnitSpace = (
|
|||
for (const el of mousePos.composedPath()) {
|
||||
if (!(el instanceof HTMLElement || el instanceof SVGElement)) break
|
||||
|
||||
if (el.hasAttribute(attributeNameThatLocksFramestamp)) {
|
||||
const val = el.getAttribute(attributeNameThatLocksFramestamp)
|
||||
if (el.hasAttribute(ATTR_LOCK_FRAMESTAMP)) {
|
||||
const val = el.getAttribute(ATTR_LOCK_FRAMESTAMP)
|
||||
if (typeof val !== 'string') continue
|
||||
if (val === 'hide') return [-1, FrameStampPositionType.hidden]
|
||||
const double = parseFloat(val)
|
||||
|
|
|
@ -11,12 +11,13 @@ import styled from 'styled-components'
|
|||
import type KeyframeEditor from './KeyframeEditor'
|
||||
import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
|
||||
import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {attributeNameThatLocksFramestamp} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {includeLockFrameStampAttrs} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||
import {
|
||||
lockedCursorCssVarName,
|
||||
useCssCursorLock,
|
||||
} from '@theatre/studio/uiComponents/PointerEventsHandler'
|
||||
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
|
||||
|
||||
export const dotSize = 6
|
||||
|
||||
|
@ -78,10 +79,8 @@ const GraphEditorDotNonScalar: React.VFC<IProps> = (props) => {
|
|||
cx: `calc(var(--unitSpaceToScaledSpaceMultiplier) * ${cur.position} * 1px)`,
|
||||
cy: `calc((var(--graphEditorVerticalSpace) - var(--graphEditorVerticalSpace) * ${cyInExtremumSpace}) * 1px)`,
|
||||
}}
|
||||
{...{
|
||||
[attributeNameThatLocksFramestamp]: cur.position.toFixed(3),
|
||||
}}
|
||||
data-pos={cur.position.toFixed(3)}
|
||||
{...includeLockFrameStampAttrs(cur.position)}
|
||||
{...DopeSnap.includePositionSnapAttrs(cur.position)}
|
||||
className={isDragging ? 'beingDragged' : ''}
|
||||
/>
|
||||
<Circle
|
||||
|
|
|
@ -11,12 +11,13 @@ import styled from 'styled-components'
|
|||
import type KeyframeEditor from './KeyframeEditor'
|
||||
import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
|
||||
import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {attributeNameThatLocksFramestamp} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {includeLockFrameStampAttrs} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||
import {
|
||||
lockedCursorCssVarName,
|
||||
useCssCursorLock,
|
||||
} from '@theatre/studio/uiComponents/PointerEventsHandler'
|
||||
import DopeSnap from '@theatre/studio/panels/SequenceEditorPanel/RightOverlay/DopeSnap'
|
||||
|
||||
export const dotSize = 6
|
||||
|
||||
|
@ -79,10 +80,8 @@ const GraphEditorDotScalar: React.VFC<IProps> = (props) => {
|
|||
cx: `calc(var(--unitSpaceToScaledSpaceMultiplier) * ${cur.position} * 1px)`,
|
||||
cy: `calc((var(--graphEditorVerticalSpace) - var(--graphEditorVerticalSpace) * ${cyInExtremumSpace}) * 1px)`,
|
||||
}}
|
||||
{...{
|
||||
[attributeNameThatLocksFramestamp]: cur.position.toFixed(3),
|
||||
}}
|
||||
data-pos={cur.position.toFixed(3)}
|
||||
{...includeLockFrameStampAttrs(cur.position)}
|
||||
{...DopeSnap.includePositionSnapAttrs(cur.position)}
|
||||
className={isDragging ? 'beingDragged' : ''}
|
||||
/>
|
||||
<Circle
|
||||
|
|
|
@ -6,7 +6,7 @@ import React, {useCallback} from 'react'
|
|||
import styled from 'styled-components'
|
||||
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||
import {VscTriangleUp} from 'react-icons/all'
|
||||
import {attributeNameThatLocksFramestamp} from './FrameStampPositionProvider'
|
||||
import {includeLockFrameStampAttrs} from './FrameStampPositionProvider'
|
||||
|
||||
const Container = styled.button`
|
||||
outline: none;
|
||||
|
@ -65,7 +65,7 @@ const GraphEditorToggle: React.FC<{
|
|||
onClick={toggle}
|
||||
title={'Toggle graph editor'}
|
||||
className={isOpen ? 'open' : ''}
|
||||
{...{[attributeNameThatLocksFramestamp]: 'hide'}}
|
||||
{...includeLockFrameStampAttrs('hide')}
|
||||
>
|
||||
<VscTriangleUp />
|
||||
</Container>
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
// Pretty much same code as for keyframe and similar for playhead.
|
||||
// Consider if we should unify the implementations.
|
||||
// - See "useLockFrameStampPosition"
|
||||
// - Also see "pointerPositionInUnitSpace" for a related impl (for different problem)
|
||||
const POSITION_SNAP_ATTR = 'data-pos'
|
||||
|
||||
/**
|
||||
* Uses `[data-pos]` attribute to understand potential snap targets.
|
||||
*/
|
||||
const DopeSnap = {
|
||||
checkIfMouseEventSnapToPos(
|
||||
event: MouseEvent,
|
||||
options?: {ignore?: Element | null},
|
||||
): number | null {
|
||||
const snapTarget = event
|
||||
.composedPath()
|
||||
.find(
|
||||
(el): el is Element =>
|
||||
el instanceof Element &&
|
||||
el !== options?.ignore &&
|
||||
el.hasAttribute(POSITION_SNAP_ATTR),
|
||||
)
|
||||
|
||||
if (snapTarget) {
|
||||
const snapPos = parseFloat(snapTarget.getAttribute(POSITION_SNAP_ATTR)!)
|
||||
if (isFinite(snapPos)) {
|
||||
return snapPos
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
/**
|
||||
* Use as a spread in a React element
|
||||
*
|
||||
* @example
|
||||
* ```tsx
|
||||
* <div {...DopeSnap.includePositionSnapAttrs(10)}/>
|
||||
* ```
|
||||
*/
|
||||
includePositionSnapAttrs(position: number) {
|
||||
return {[POSITION_SNAP_ATTR]: position}
|
||||
},
|
||||
}
|
||||
|
||||
export default DopeSnap
|
|
@ -18,11 +18,12 @@ import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
|||
import React, {useMemo} from 'react'
|
||||
import styled from 'styled-components'
|
||||
import {
|
||||
attributeNameThatLocksFramestamp,
|
||||
includeLockFrameStampAttrs,
|
||||
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'}>`
|
||||
position: absolute;
|
||||
|
@ -199,30 +200,17 @@ const FocusRangeThumb: React.FC<{
|
|||
)
|
||||
},
|
||||
onDrag(dx, _, event) {
|
||||
range = existingRangeD.getValue()?.range || defaultRange
|
||||
|
||||
const deltaPos = scaledSpaceToUnitSpace(dx)
|
||||
let newPosition: number
|
||||
const oldPosPlusDeltaPos = posBeforeDrag + deltaPos
|
||||
|
||||
// Enable snapping
|
||||
const snapTarget = event
|
||||
.composedPath()
|
||||
.find(
|
||||
(el): el is Element =>
|
||||
el instanceof Element &&
|
||||
el !== hitZoneNode &&
|
||||
el.hasAttribute('data-pos'),
|
||||
)
|
||||
|
||||
if (snapTarget) {
|
||||
const snapPos = parseFloat(snapTarget.getAttribute('data-pos')!)
|
||||
|
||||
if (isFinite(snapPos)) {
|
||||
newPosition = snapPos
|
||||
}
|
||||
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
|
||||
|
@ -263,11 +251,8 @@ const FocusRangeThumb: React.FC<{
|
|||
})
|
||||
},
|
||||
onDragEnd(dragHappened) {
|
||||
if (dragHappened && tempTransaction !== undefined) {
|
||||
tempTransaction.commit()
|
||||
} else if (tempTransaction) {
|
||||
tempTransaction.discard()
|
||||
}
|
||||
if (dragHappened) tempTransaction?.commit()
|
||||
else tempTransaction?.discard()
|
||||
},
|
||||
}
|
||||
}, [layoutP])
|
||||
|
@ -305,10 +290,8 @@ const FocusRangeThumb: React.FC<{
|
|||
return (
|
||||
<TheDiv
|
||||
ref={hitZoneRef as $IntentionalAny}
|
||||
data-pos={position.toFixed(3)}
|
||||
{...{
|
||||
[attributeNameThatLocksFramestamp]: position.toFixed(3),
|
||||
}}
|
||||
{...DopeSnap.includePositionSnapAttrs(position)}
|
||||
{...includeLockFrameStampAttrs(position)}
|
||||
className={`${isDragging && 'dragging'} ${enabled && 'enabled'}`}
|
||||
enabled={enabled}
|
||||
type={thumbType}
|
||||
|
|
|
@ -9,7 +9,7 @@ import {position} from 'polished'
|
|||
import React, {useCallback, useMemo, useState} from 'react'
|
||||
import styled from 'styled-components'
|
||||
import {zIndexes} from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel'
|
||||
import {attributeNameThatLocksFramestamp} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {includeLockFrameStampAttrs} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||
|
||||
const Container = styled.div`
|
||||
|
@ -241,7 +241,7 @@ const HorizontalScrollbar: React.FC<{
|
|||
return (
|
||||
<Container
|
||||
style={{bottom: bottom + 8 + 'px'}}
|
||||
{...{[attributeNameThatLocksFramestamp]: 'hide'}}
|
||||
{...includeLockFrameStampAttrs('hide')}
|
||||
>
|
||||
<TimeThread>
|
||||
<DraggableArea
|
||||
|
|
|
@ -12,7 +12,7 @@ import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
|||
import React, {useMemo, useRef} from 'react'
|
||||
import styled from 'styled-components'
|
||||
import {
|
||||
attributeNameThatLocksFramestamp,
|
||||
includeLockFrameStampAttrs,
|
||||
useLockFrameStampPosition,
|
||||
} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||
|
@ -25,6 +25,7 @@ import type {UseDragOpts} from '@theatre/studio/uiComponents/useDrag'
|
|||
import type {CommitOrDiscard} from '@theatre/studio/StudioStore/StudioStore'
|
||||
import type {StudioHistoricStateSequenceEditorMarker} from '@theatre/studio/store/types'
|
||||
import {zIndexes} from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel'
|
||||
import DopeSnap from './DopeSnap'
|
||||
|
||||
const MARKER_SIZE_W_PX = 10
|
||||
const MARKER_SIZE_H_PX = 8
|
||||
|
@ -156,16 +157,13 @@ const MarkerDotDefined: React.VFC<IMarkerDotDefinedProps> = ({
|
|||
{contextMenu}
|
||||
<HitZone
|
||||
ref={markRef}
|
||||
// `data-pos` and `attributeNameThatLocksFramestamp` are used by FrameStampPositionProvider
|
||||
// `data-pos` and `includeLockFrameStampAttrs` are used by FrameStampPositionProvider
|
||||
// in order to handle snapping the playhead. Adding these props effectively
|
||||
// causes the playhead to "snap" to the marker on mouse over.
|
||||
// `pointerEventsAutoInNormalMode` and `lockedCursorCssVarName` in the CSS above are also
|
||||
// used to make this behave correctly.
|
||||
{...{
|
||||
[attributeNameThatLocksFramestamp]: marker.position.toFixed(3),
|
||||
[POSITION_SNAPPING.attributeNameForPosition]:
|
||||
marker.position.toFixed(3),
|
||||
}}
|
||||
{...includeLockFrameStampAttrs(marker.position)}
|
||||
{...DopeSnap.includePositionSnapAttrs(marker.position)}
|
||||
className={isDragging ? 'beingDragged' : ''}
|
||||
/>
|
||||
<MarkerVisualDot />
|
||||
|
@ -227,7 +225,7 @@ function useDragMarker(
|
|||
const original = markerAtStartOfDrag
|
||||
const newPosition = Math.max(
|
||||
// check if our event hoversover a [data-pos] element
|
||||
POSITION_SNAPPING.checkIfMouseEventSnapToPos(event, {
|
||||
DopeSnap.checkIfMouseEventSnapToPos(event, {
|
||||
ignore: node,
|
||||
}) ??
|
||||
// if we don't find snapping target, check the distance dragged + original position
|
||||
|
@ -267,39 +265,3 @@ function useDragMarker(
|
|||
|
||||
return [isDragging]
|
||||
}
|
||||
|
||||
// Pretty much same code as for keyframe and similar for playhead.
|
||||
// Consider if we should unify the implementations.
|
||||
// - See "useLockFrameStampPosition"
|
||||
// - Also see "pointerPositionInUnitSpace" for a related impl (for different problem)
|
||||
const POSITION_SNAPPING = {
|
||||
/**
|
||||
* Used to indicate that when hovering over this element, we should enable
|
||||
* snapping to the given position.
|
||||
*/
|
||||
attributeNameForPosition: 'data-pos',
|
||||
checkIfMouseEventSnapToPos(
|
||||
event: MouseEvent,
|
||||
options?: {ignore?: HTMLElement | null},
|
||||
): number | null {
|
||||
const snapTarget = event
|
||||
.composedPath()
|
||||
.find(
|
||||
(el): el is Element =>
|
||||
el instanceof Element &&
|
||||
el !== options?.ignore &&
|
||||
el.hasAttribute(POSITION_SNAPPING.attributeNameForPosition),
|
||||
)
|
||||
|
||||
if (snapTarget) {
|
||||
const snapPos = parseFloat(
|
||||
snapTarget.getAttribute(POSITION_SNAPPING.attributeNameForPosition)!,
|
||||
)
|
||||
if (isFinite(snapPos)) {
|
||||
return snapPos
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import React, {useMemo} from 'react'
|
|||
import styled from 'styled-components'
|
||||
import {zIndexes} from '@theatre/studio/panels/SequenceEditorPanel/SequenceEditorPanel'
|
||||
import {
|
||||
attributeNameThatLocksFramestamp,
|
||||
includeLockFrameStampAttrs,
|
||||
useLockFrameStampPosition,
|
||||
} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||
|
@ -27,6 +27,7 @@ import {
|
|||
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
|
||||
import getStudio from '@theatre/studio/getStudio'
|
||||
import {generateSequenceMarkerId} from '@theatre/shared/utils/ids'
|
||||
import DopeSnap from './DopeSnap'
|
||||
|
||||
const Container = styled.div<{isVisible: boolean}>`
|
||||
--thumbColor: #00e0ff;
|
||||
|
@ -218,27 +219,13 @@ const Playhead: React.FC<{layoutP: Pointer<SequenceEditorPanelLayout>}> = ({
|
|||
},
|
||||
onDrag(dx, _, event) {
|
||||
const deltaPos = scaledSpaceToUnitSpace(dx)
|
||||
const unsnappedPos = clamp(posBeforeSeek + deltaPos, 0, sequence.length)
|
||||
|
||||
let newPosition = unsnappedPos
|
||||
|
||||
const snapTarget = event
|
||||
.composedPath()
|
||||
.find(
|
||||
(el): el is Element =>
|
||||
el instanceof Element &&
|
||||
el !== thumbNode &&
|
||||
el.hasAttribute('data-pos'),
|
||||
)
|
||||
|
||||
if (snapTarget) {
|
||||
const snapPos = parseFloat(snapTarget.getAttribute('data-pos')!)
|
||||
if (isFinite(snapPos)) {
|
||||
newPosition = snapPos
|
||||
}
|
||||
}
|
||||
|
||||
sequence.position = newPosition
|
||||
sequence.position =
|
||||
DopeSnap.checkIfMouseEventSnapToPos(event, {
|
||||
ignore: thumbNode,
|
||||
}) ??
|
||||
// unsnapped
|
||||
clamp(posBeforeSeek + deltaPos, 0, sequence.length)
|
||||
},
|
||||
onDragEnd() {
|
||||
setIsSeeking(false)
|
||||
|
@ -286,11 +273,11 @@ const Playhead: React.FC<{layoutP: Pointer<SequenceEditorPanelLayout>}> = ({
|
|||
className={`${isSeeking && 'seeking'} ${
|
||||
isPlayheadAttachedToFocusRange && 'playheadattachedtofocusrange'
|
||||
}`}
|
||||
{...{[attributeNameThatLocksFramestamp]: 'hide'}}
|
||||
{...includeLockFrameStampAttrs('hide')}
|
||||
>
|
||||
<Thumb
|
||||
ref={thumbRef as $IntentionalAny}
|
||||
data-pos={posInUnitSpace.toFixed(3)}
|
||||
{...DopeSnap.includePositionSnapAttrs(posInUnitSpace)}
|
||||
onClick={(e) => {
|
||||
openPopover(e, thumbNode!)
|
||||
}}
|
||||
|
@ -305,7 +292,7 @@ const Playhead: React.FC<{layoutP: Pointer<SequenceEditorPanelLayout>}> = ({
|
|||
</Thumb>
|
||||
|
||||
<Rod
|
||||
data-pos={posInUnitSpace.toFixed(3)}
|
||||
{...DopeSnap.includePositionSnapAttrs(posInUnitSpace)}
|
||||
className={isSeeking ? 'seeking' : ''}
|
||||
/>
|
||||
</Container>
|
||||
|
|
|
@ -4,7 +4,7 @@ import React from 'react'
|
|||
import styled from 'styled-components'
|
||||
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
|
||||
import StampsGrid from '@theatre/studio/panels/SequenceEditorPanel/FrameGrid/StampsGrid'
|
||||
import {attributeNameThatLocksFramestamp} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {includeLockFrameStampAttrs} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||
import FocusRangeZone from './FocusRangeZone/FocusRangeZone'
|
||||
|
||||
|
@ -34,7 +34,7 @@ const TopStrip: React.FC<{layoutP: Pointer<SequenceEditorPanelLayout>}> = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<Container {...{[attributeNameThatLocksFramestamp]: 'hide'}}>
|
||||
<Container {...includeLockFrameStampAttrs('hide')}>
|
||||
<StampsGrid layoutP={layoutP} width={width} height={topStripHeight} />
|
||||
<FocusRangeZone layoutP={layoutP} />
|
||||
</Container>
|
||||
|
|
|
@ -98,12 +98,10 @@ export default function useDrag(
|
|||
}
|
||||
|
||||
const dragEndHandler = () => {
|
||||
if (modeRef.current === 'dragging') {
|
||||
removeDragListeners()
|
||||
modeRef.current = 'notDragging'
|
||||
removeDragListeners()
|
||||
modeRef.current = 'notDragging'
|
||||
|
||||
optsRef.current.onDragEnd?.(stateRef.current.dragHappened)
|
||||
}
|
||||
optsRef.current.onDragEnd?.(stateRef.current.dragHappened)
|
||||
}
|
||||
|
||||
const addDragListeners = () => {
|
||||
|
|
Loading…
Reference in a new issue