Compound prop context menu (#157)
This commit is contained in:
parent
cfbb6ab043
commit
d83d2b558c
13 changed files with 549 additions and 114 deletions
|
@ -54,5 +54,71 @@ export function valueInProp<PropConfig extends PropTypeConfig_AllSimples>(
|
||||||
export function isPropConfSequencable(
|
export function isPropConfSequencable(
|
||||||
conf: PropTypeConfig,
|
conf: PropTypeConfig,
|
||||||
): conf is Extract<PropTypeConfig, {interpolate: any}> {
|
): conf is Extract<PropTypeConfig, {interpolate: any}> {
|
||||||
return Object.prototype.hasOwnProperty.call(conf, 'interpolate')
|
return !isPropConfigComposite(conf) // now all non-compounds are sequencable
|
||||||
|
}
|
||||||
|
|
||||||
|
const compoundPropSequenceabilityCache = new WeakMap<
|
||||||
|
PropTypeConfig_Compound<{}> | PropTypeConfig_Enum,
|
||||||
|
boolean
|
||||||
|
>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See {@link compoundHasSimpleDescendantsImpl}
|
||||||
|
*/
|
||||||
|
export function compoundHasSimpleDescendants(
|
||||||
|
conf: PropTypeConfig_Compound<{}> | PropTypeConfig_Enum,
|
||||||
|
): boolean {
|
||||||
|
if (!compoundPropSequenceabilityCache.has(conf)) {
|
||||||
|
compoundPropSequenceabilityCache.set(
|
||||||
|
conf,
|
||||||
|
compoundHasSimpleDescendantsImpl(conf),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return compoundPropSequenceabilityCache.get(conf)!
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This basically checks of the compound prop has at least one simple prop in its descendants.
|
||||||
|
* In other words, if the compound props has no subs, or its subs are only compounds that eventually
|
||||||
|
* don't have simple subs, this will return false.
|
||||||
|
*/
|
||||||
|
function compoundHasSimpleDescendantsImpl(
|
||||||
|
conf: PropTypeConfig_Compound<{}> | PropTypeConfig_Enum,
|
||||||
|
): boolean {
|
||||||
|
if (conf.type === 'enum') {
|
||||||
|
throw new Error(`Not implemented yet for enums`)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key in conf.props) {
|
||||||
|
const subConf = conf.props[
|
||||||
|
key as $IntentionalAny as keyof typeof conf.props
|
||||||
|
] as PropTypeConfig
|
||||||
|
if (isPropConfigComposite(subConf)) {
|
||||||
|
if (compoundHasSimpleDescendants(subConf)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function* iteratePropType(
|
||||||
|
conf: PropTypeConfig,
|
||||||
|
pathUpToThisPoint: PathToProp,
|
||||||
|
): Generator<{path: PathToProp; conf: PropTypeConfig}, void, void> {
|
||||||
|
if (conf.type === 'compound') {
|
||||||
|
for (const key in conf.props) {
|
||||||
|
yield* iteratePropType(conf.props[key] as PropTypeConfig, [
|
||||||
|
...pathUpToThisPoint,
|
||||||
|
key,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
} else if (conf.type === 'enum') {
|
||||||
|
throw new Error(`Not implemented yet`)
|
||||||
|
} else {
|
||||||
|
return yield {path: pathUpToThisPoint, conf}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,9 @@ function cloneDeepSerializableAndPrune<T>(v: T): T | undefined {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO replace with {@link iteratePropType}
|
||||||
|
*/
|
||||||
function forEachDeepSimplePropOfCompoundProp(
|
function forEachDeepSimplePropOfCompoundProp(
|
||||||
propType: PropTypeConfig_Compound<$IntentionalAny>,
|
propType: PropTypeConfig_Compound<$IntentionalAny>,
|
||||||
path: Array<string | number>,
|
path: Array<string | number>,
|
||||||
|
|
|
@ -39,8 +39,6 @@ const ExtensionPaneWrapper: React.FC<{
|
||||||
}
|
}
|
||||||
|
|
||||||
const Container = styled(PanelWrapper)`
|
const Container = styled(PanelWrapper)`
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import type {PropTypeConfig_Compound} from '@theatre/core/propTypes'
|
import type {PropTypeConfig_Compound} from '@theatre/core/propTypes'
|
||||||
import {isPropConfigComposite} from '@theatre/shared/propTypes/utils'
|
import {isPropConfigComposite} from '@theatre/shared/propTypes/utils'
|
||||||
|
import type {$FixMe} from '@theatre/shared/utils/types'
|
||||||
import {getPointerParts} from '@theatre/dataverse'
|
import {getPointerParts} from '@theatre/dataverse'
|
||||||
import type {Pointer} from '@theatre/dataverse'
|
import type {Pointer} from '@theatre/dataverse'
|
||||||
import last from 'lodash-es/last'
|
import last from 'lodash-es/last'
|
||||||
|
@ -8,12 +9,13 @@ import React from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
import {indentationFormula} from '@theatre/studio/panels/DetailPanel/DeterminePropEditorForDetail/SingleRowPropEditor'
|
import {indentationFormula} from '@theatre/studio/panels/DetailPanel/DeterminePropEditorForDetail/SingleRowPropEditor'
|
||||||
import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS'
|
import {propNameTextCSS} from '@theatre/studio/propEditors/utils/propNameTextCSS'
|
||||||
import DefaultOrStaticValueIndicator from '@theatre/studio/propEditors/DefaultValueIndicator'
|
|
||||||
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||||
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
||||||
import DeterminePropEditorForDetail from '@theatre/studio/panels/DetailPanel/DeterminePropEditorForDetail'
|
import DeterminePropEditorForDetail from '@theatre/studio/panels/DetailPanel/DeterminePropEditorForDetail'
|
||||||
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
||||||
import type {$FixMe} from '@theatre/shared/utils/types'
|
|
||||||
|
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
|
||||||
|
import {useEditingToolsForCompoundProp} from '@theatre/studio/propEditors/useEditingToolsForCompoundProp'
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
--step: 8px;
|
--step: 8px;
|
||||||
|
@ -42,7 +44,7 @@ const PropName = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
&:hover {
|
&:hover {
|
||||||
/* color: white; */
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
${() => propNameTextCSS};
|
${() => propNameTextCSS};
|
||||||
|
@ -85,18 +87,29 @@ function DetailCompoundPropEditor<
|
||||||
const [propNameContainerRef, propNameContainer] =
|
const [propNameContainerRef, propNameContainer] =
|
||||||
useRefAndState<HTMLDivElement | null>(null)
|
useRefAndState<HTMLDivElement | null>(null)
|
||||||
|
|
||||||
|
const tools = useEditingToolsForCompoundProp(
|
||||||
|
pointerToProp as $FixMe,
|
||||||
|
obj,
|
||||||
|
propConfig,
|
||||||
|
)
|
||||||
|
|
||||||
|
const [contextMenu] = useContextMenu(propNameContainer, {
|
||||||
|
menuItems: tools.contextMenuItems,
|
||||||
|
})
|
||||||
|
|
||||||
const lastSubPropIsComposite = compositeSubs.length > 0
|
const lastSubPropIsComposite = compositeSubs.length > 0
|
||||||
|
|
||||||
// previous versions of the DetailCompoundPropEditor had a context menu item for "Reset values".
|
// previous versions of the DetailCompoundPropEditor had a context menu item for "Reset values".
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
|
{contextMenu}
|
||||||
<Header
|
<Header
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
style={{'--depth': visualIndentation - 1}}
|
style={{'--depth': visualIndentation - 1}}
|
||||||
>
|
>
|
||||||
<Padding>
|
<Padding>
|
||||||
<DefaultOrStaticValueIndicator hasStaticOverride={false} />
|
{tools.controlIndicators}
|
||||||
<PropName ref={propNameContainerRef}>{propName || 'Props'}</PropName>
|
<PropName ref={propNameContainerRef}>{propName || 'Props'}</PropName>
|
||||||
</Padding>
|
</Padding>
|
||||||
</Header>
|
</Header>
|
||||||
|
|
|
@ -35,6 +35,17 @@ const Children = styled.ul`
|
||||||
list-style: none;
|
list-style: none;
|
||||||
`
|
`
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @remarks
|
||||||
|
* Right now, we're rendering a hierarchical dom tree that reflects the hierarchy of
|
||||||
|
* objects, compound props, and their subs. This is not necessary and makes styling complicated.
|
||||||
|
* Instead of this, we can simply render a list. This should be easy to do, since the view model
|
||||||
|
* in {@link calculateSequenceEditorTree} already includes all the vertical placement information
|
||||||
|
* (height and top) we need to render the nodes as a list.
|
||||||
|
*
|
||||||
|
* Note that we don't need to change {@link calculateSequenceEditorTree} to be list-based. It can
|
||||||
|
* retain its hierarchy. It's just the DOM tree that should be list-based.
|
||||||
|
*/
|
||||||
const RightRow: React.FC<{
|
const RightRow: React.FC<{
|
||||||
leaf: SequenceEditorTree_Row<unknown>
|
leaf: SequenceEditorTree_Row<unknown>
|
||||||
node: React.ReactElement
|
node: React.ReactElement
|
||||||
|
|
|
@ -12,6 +12,7 @@ import useDrag from '@theatre/studio/uiComponents/useDrag'
|
||||||
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
import useRefAndState from '@theatre/studio/utils/useRefAndState'
|
||||||
import React, {useMemo} from 'react'
|
import React, {useMemo} from 'react'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
import {useLockFrameStampPosition} from '@theatre/studio/panels/SequenceEditorPanel/FrameStampPositionProvider'
|
||||||
|
|
||||||
export const focusRangeStripTheme = {
|
export const focusRangeStripTheme = {
|
||||||
enabled: {
|
enabled: {
|
||||||
|
@ -175,7 +176,6 @@ const FocusRangeStrip: React.FC<{
|
||||||
})
|
})
|
||||||
|
|
||||||
const scaledSpaceToUnitSpace = useVal(layoutP.scaledSpace.toUnitSpace)
|
const scaledSpaceToUnitSpace = useVal(layoutP.scaledSpace.toUnitSpace)
|
||||||
const [isDraggingRef, isDragging] = useRefAndState(false)
|
|
||||||
const sheet = useVal(layoutP.sheet)
|
const sheet = useVal(layoutP.sheet)
|
||||||
|
|
||||||
const gestureHandlers = useMemo((): Parameters<typeof useDrag>[1] => {
|
const gestureHandlers = useMemo((): Parameters<typeof useDrag>[1] => {
|
||||||
|
@ -192,7 +192,6 @@ const FocusRangeStrip: React.FC<{
|
||||||
const endPosBeforeDrag = existingRange.range.end
|
const endPosBeforeDrag = existingRange.range.end
|
||||||
let dragHappened = false
|
let dragHappened = false
|
||||||
const sequence = val(layoutP.sheet).getSequence()
|
const sequence = val(layoutP.sheet).getSequence()
|
||||||
isDraggingRef.current = true
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
onDrag(dx) {
|
onDrag(dx) {
|
||||||
|
@ -234,7 +233,6 @@ const FocusRangeStrip: React.FC<{
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDragEnd() {
|
onDragEnd() {
|
||||||
isDraggingRef.current = false
|
|
||||||
if (existingRange) {
|
if (existingRange) {
|
||||||
if (dragHappened && tempTransaction !== undefined) {
|
if (dragHappened && tempTransaction !== undefined) {
|
||||||
tempTransaction.commit()
|
tempTransaction.commit()
|
||||||
|
@ -250,7 +248,9 @@ const FocusRangeStrip: React.FC<{
|
||||||
}
|
}
|
||||||
}, [sheet, scaledSpaceToUnitSpace])
|
}, [sheet, scaledSpaceToUnitSpace])
|
||||||
|
|
||||||
useDrag(rangeStripNode, gestureHandlers)
|
const [isDragging] = useDrag(rangeStripNode, gestureHandlers)
|
||||||
|
|
||||||
|
useLockFrameStampPosition(isDragging, -1)
|
||||||
|
|
||||||
return usePrism(() => {
|
return usePrism(() => {
|
||||||
const existingRange = existingRangeD.getValue()
|
const existingRange = existingRangeD.getValue()
|
||||||
|
|
|
@ -186,30 +186,28 @@ const FocusRangeThumb: React.FC<{
|
||||||
const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, {
|
const snapPos = DopeSnap.checkIfMouseEventSnapToPos(event, {
|
||||||
ignore: hitZoneNode,
|
ignore: hitZoneNode,
|
||||||
})
|
})
|
||||||
if (snapPos != null) {
|
|
||||||
|
if (snapPos == null) {
|
||||||
|
const deltaPos = scaledSpaceToUnitSpace(dx)
|
||||||
|
const oldPosPlusDeltaPos = posBeforeDrag + deltaPos
|
||||||
|
newPosition = oldPosPlusDeltaPos
|
||||||
|
} else {
|
||||||
newPosition = snapPos
|
newPosition = snapPos
|
||||||
}
|
}
|
||||||
|
|
||||||
range = existingRangeD.getValue()?.range || defaultRange
|
range = existingRangeD.getValue()?.range || defaultRange
|
||||||
const deltaPos = scaledSpaceToUnitSpace(dx)
|
|
||||||
const oldPosPlusDeltaPos = posBeforeDrag + deltaPos
|
|
||||||
// Make sure that the focus range has a minimal width
|
// Make sure that the focus range has a minimal width
|
||||||
if (thumbType === 'start') {
|
if (thumbType === 'start') {
|
||||||
// Prevent the start thumb from going below 0
|
// Prevent the start thumb from going below 0
|
||||||
newPosition = Math.max(
|
newPosition = Math.max(
|
||||||
Math.min(
|
Math.min(newPosition, range['end'] - minFocusRangeStripWidth),
|
||||||
oldPosPlusDeltaPos,
|
|
||||||
range['end'] - minFocusRangeStripWidth,
|
|
||||||
),
|
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// Prevent the start thumb from going over the length of the sequence
|
// Prevent the start thumb from going over the length of the sequence
|
||||||
newPosition = Math.min(
|
newPosition = Math.min(
|
||||||
Math.max(
|
Math.max(newPosition, range['start'] + minFocusRangeStripWidth),
|
||||||
oldPosPlusDeltaPos,
|
|
||||||
range['start'] + minFocusRangeStripWidth,
|
|
||||||
),
|
|
||||||
sheet.getSequence().length,
|
sheet.getSequence().length,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -223,9 +223,12 @@ const Playhead: React.FC<{layoutP: Pointer<SequenceEditorPanelLayout>}> = ({
|
||||||
// unsnapped
|
// unsnapped
|
||||||
clamp(posBeforeSeek + deltaPos, 0, sequence.length)
|
clamp(posBeforeSeek + deltaPos, 0, sequence.length)
|
||||||
},
|
},
|
||||||
onDragEnd() {
|
onDragEnd(dragHappened) {
|
||||||
setIsSeeking(false)
|
setIsSeeking(false)
|
||||||
},
|
},
|
||||||
|
onClick(e) {
|
||||||
|
openPopover(e, thumbRef.current!)
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -276,9 +279,6 @@ const Playhead: React.FC<{layoutP: Pointer<SequenceEditorPanelLayout>}> = ({
|
||||||
<Thumb
|
<Thumb
|
||||||
ref={thumbRef as $IntentionalAny}
|
ref={thumbRef as $IntentionalAny}
|
||||||
{...DopeSnap.includePositionSnapAttrs(posInUnitSpace)}
|
{...DopeSnap.includePositionSnapAttrs(posInUnitSpace)}
|
||||||
onClick={(e) => {
|
|
||||||
openPopover(e, thumbNode!)
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<RoomToClick room={8} />
|
<RoomToClick room={8} />
|
||||||
<Squinch />
|
<Squinch />
|
||||||
|
|
|
@ -1,9 +1,16 @@
|
||||||
import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
|
import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
|
||||||
|
import type {VoidFn} from '@theatre/shared/utils/types'
|
||||||
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
import {pointerEventsAutoInNormalMode} from '@theatre/studio/css'
|
||||||
import {transparentize} from 'polished'
|
import {transparentize} from 'polished'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import styled, {css} from 'styled-components'
|
import styled, {css} from 'styled-components'
|
||||||
|
|
||||||
|
export type NearbyKeyframesControls = {
|
||||||
|
prev?: Pick<Keyframe, 'position'> & {jump: VoidFn}
|
||||||
|
cur: {type: 'on'; toggle: VoidFn} | {type: 'off'; toggle: VoidFn}
|
||||||
|
next?: Pick<Keyframe, 'position'> & {jump: VoidFn}
|
||||||
|
}
|
||||||
|
|
||||||
const Container = styled.div`
|
const Container = styled.div`
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
@ -158,41 +165,16 @@ namespace Icons {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const NextPrevKeyframeCursors: React.FC<{
|
const NextPrevKeyframeCursors: React.FC<NearbyKeyframesControls> = (props) => {
|
||||||
prev?: Keyframe
|
|
||||||
cur?: Keyframe
|
|
||||||
next?: Keyframe
|
|
||||||
jumpToPosition: (position: number) => void
|
|
||||||
toggleKeyframeOnCurrentPosition: () => void
|
|
||||||
}> = (props) => {
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
<Prev
|
<Prev available={!!props.prev} onClick={props.prev?.jump}>
|
||||||
available={!!props.prev}
|
|
||||||
onClick={() => {
|
|
||||||
if (props.prev) {
|
|
||||||
props.jumpToPosition(props.prev.position)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icons.Prev />
|
<Icons.Prev />
|
||||||
</Prev>
|
</Prev>
|
||||||
<CurButton
|
<CurButton isOn={props.cur.type === 'on'} onClick={props.cur.toggle}>
|
||||||
isOn={!!props.cur}
|
|
||||||
onClick={() => {
|
|
||||||
props.toggleKeyframeOnCurrentPosition()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icons.Cur />
|
<Icons.Cur />
|
||||||
</CurButton>
|
</CurButton>
|
||||||
<Next
|
<Next available={!!props.next} onClick={props.next?.jump}>
|
||||||
available={!!props.next}
|
|
||||||
onClick={() => {
|
|
||||||
if (props.next) {
|
|
||||||
props.jumpToPosition(props.next.position)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Icons.Next />
|
<Icons.Next />
|
||||||
</Next>
|
</Next>
|
||||||
</Container>
|
</Container>
|
||||||
|
|
59
theatre/studio/src/propEditors/getNearbyKeyframesOfTrack.tsx
Normal file
59
theatre/studio/src/propEditors/getNearbyKeyframesOfTrack.tsx
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
import type {
|
||||||
|
TrackData,
|
||||||
|
Keyframe,
|
||||||
|
} from '@theatre/core/projects/store/types/SheetState_Historic'
|
||||||
|
import last from 'lodash-es/last'
|
||||||
|
|
||||||
|
const cache = new WeakMap<
|
||||||
|
TrackData,
|
||||||
|
[seqPosition: number, nearbyKeyframes: NearbyKeyframes]
|
||||||
|
>()
|
||||||
|
|
||||||
|
const noKeyframes: NearbyKeyframes = {}
|
||||||
|
|
||||||
|
export function getNearbyKeyframesOfTrack(
|
||||||
|
track: TrackData | undefined,
|
||||||
|
sequencePosition: number,
|
||||||
|
): NearbyKeyframes {
|
||||||
|
if (!track || track.keyframes.length === 0) return noKeyframes
|
||||||
|
|
||||||
|
const cachedItem = cache.get(track)
|
||||||
|
if (cachedItem && cachedItem[0] === sequencePosition) {
|
||||||
|
return cachedItem[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
const calculate = (): NearbyKeyframes => {
|
||||||
|
const i = track.keyframes.findIndex((kf) => kf.position >= sequencePosition)
|
||||||
|
|
||||||
|
if (i === -1)
|
||||||
|
return {
|
||||||
|
prev: last(track.keyframes),
|
||||||
|
}
|
||||||
|
|
||||||
|
const k = track.keyframes[i]!
|
||||||
|
if (k.position === sequencePosition) {
|
||||||
|
return {
|
||||||
|
prev: i > 0 ? track.keyframes[i - 1] : undefined,
|
||||||
|
cur: k,
|
||||||
|
next:
|
||||||
|
i === track.keyframes.length - 1 ? undefined : track.keyframes[i + 1],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
next: k,
|
||||||
|
prev: i > 0 ? track.keyframes[i - 1] : undefined,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = calculate()
|
||||||
|
cache.set(track, [sequencePosition, result])
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export type NearbyKeyframes = {
|
||||||
|
prev?: Keyframe
|
||||||
|
cur?: Keyframe
|
||||||
|
next?: Keyframe
|
||||||
|
}
|
|
@ -0,0 +1,316 @@
|
||||||
|
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
||||||
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
|
import type {IContextMenuItem} from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
|
||||||
|
import getDeep from '@theatre/shared/utils/getDeep'
|
||||||
|
import {usePrism} from '@theatre/react'
|
||||||
|
import type {
|
||||||
|
$IntentionalAny,
|
||||||
|
SerializablePrimitive,
|
||||||
|
} from '@theatre/shared/utils/types'
|
||||||
|
import {getPointerParts, prism, val} from '@theatre/dataverse'
|
||||||
|
import type {Pointer} from '@theatre/dataverse'
|
||||||
|
import get from 'lodash-es/get'
|
||||||
|
import React from 'react'
|
||||||
|
import DefaultOrStaticValueIndicator from './DefaultValueIndicator'
|
||||||
|
import type {PropTypeConfig_Compound} from '@theatre/core/propTypes'
|
||||||
|
import {
|
||||||
|
compoundHasSimpleDescendants,
|
||||||
|
isPropConfigComposite,
|
||||||
|
iteratePropType,
|
||||||
|
} from '@theatre/shared/propTypes/utils'
|
||||||
|
import type {SequenceTrackId} from '@theatre/shared/utils/ids'
|
||||||
|
import type {IPropPathToTrackIdTree} from '@theatre/core/sheetObjects/SheetObjectTemplate'
|
||||||
|
import pointerDeep from '@theatre/shared/utils/pointerDeep'
|
||||||
|
import type {NearbyKeyframesControls} from './NextPrevKeyframeCursors'
|
||||||
|
import NextPrevKeyframeCursors from './NextPrevKeyframeCursors'
|
||||||
|
import {getNearbyKeyframesOfTrack} from './getNearbyKeyframesOfTrack'
|
||||||
|
|
||||||
|
interface CommonStuff {
|
||||||
|
beingScrubbed: boolean
|
||||||
|
contextMenuItems: Array<IContextMenuItem>
|
||||||
|
controlIndicators: React.ReactElement
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For compounds that have _no_ sequenced track in all of their descendants
|
||||||
|
*/
|
||||||
|
interface AllStatic extends CommonStuff {
|
||||||
|
type: 'AllStatic'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For compounds that have at least one sequenced track in their descendants
|
||||||
|
*/
|
||||||
|
interface HasSequences extends CommonStuff {
|
||||||
|
type: 'HasSequences'
|
||||||
|
}
|
||||||
|
|
||||||
|
type Stuff = AllStatic | HasSequences
|
||||||
|
|
||||||
|
export function useEditingToolsForCompoundProp<T extends SerializablePrimitive>(
|
||||||
|
pointerToProp: Pointer<{}>,
|
||||||
|
obj: SheetObject,
|
||||||
|
propConfig: PropTypeConfig_Compound<{}>,
|
||||||
|
): Stuff {
|
||||||
|
return usePrism((): Stuff => {
|
||||||
|
// if the compound has no simple descendants, then there isn't much the user can do with it
|
||||||
|
if (!compoundHasSimpleDescendants(propConfig)) {
|
||||||
|
return {
|
||||||
|
type: 'AllStatic',
|
||||||
|
beingScrubbed: false,
|
||||||
|
contextMenuItems: [],
|
||||||
|
controlIndicators: (
|
||||||
|
<DefaultOrStaticValueIndicator hasStaticOverride={false} />
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const pathToProp = getPointerParts(pointerToProp).path
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO This implementation is wrong because {@link stateEditors.studio.ephemeral.projects.stateByProjectId.stateBySheetId.stateByObjectKey.propsBeingScrubbed.flag}
|
||||||
|
* does not prune empty objects
|
||||||
|
*/
|
||||||
|
const someDescendantsBeingScrubbed = !!val(
|
||||||
|
get(
|
||||||
|
getStudio()!.atomP.ephemeral.projects.stateByProjectId[
|
||||||
|
obj.address.projectId
|
||||||
|
].stateBySheetId[obj.address.sheetId].stateByObjectKey[
|
||||||
|
obj.address.objectKey
|
||||||
|
].valuesBeingScrubbed,
|
||||||
|
getPointerParts(pointerToProp).path,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
const contextMenuItems: IContextMenuItem[] = []
|
||||||
|
|
||||||
|
const common: CommonStuff = {
|
||||||
|
beingScrubbed: someDescendantsBeingScrubbed,
|
||||||
|
contextMenuItems,
|
||||||
|
controlIndicators: <></>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const validSequencedTracks = val(
|
||||||
|
obj.template.getMapOfValidSequenceTracks_forStudio(),
|
||||||
|
)
|
||||||
|
|
||||||
|
const possibleSequenceTrackIds = getDeep(
|
||||||
|
validSequencedTracks,
|
||||||
|
pathToProp,
|
||||||
|
) as undefined | IPropPathToTrackIdTree
|
||||||
|
|
||||||
|
const hasOneOrMoreSequencedTracks = !!possibleSequenceTrackIds
|
||||||
|
const listOfDescendantTrackIds: SequenceTrackId[] = []
|
||||||
|
|
||||||
|
let hasOneOrMoreStatics = true
|
||||||
|
if (hasOneOrMoreSequencedTracks) {
|
||||||
|
hasOneOrMoreStatics = false
|
||||||
|
for (const descendant of iteratePropType(propConfig, [])) {
|
||||||
|
if (isPropConfigComposite(descendant.conf)) continue
|
||||||
|
const sequencedTrackIdBelongingToDescendant = getDeep(
|
||||||
|
possibleSequenceTrackIds,
|
||||||
|
descendant.path,
|
||||||
|
) as SequenceTrackId | undefined
|
||||||
|
if (typeof sequencedTrackIdBelongingToDescendant !== 'string') {
|
||||||
|
hasOneOrMoreStatics = true
|
||||||
|
} else {
|
||||||
|
listOfDescendantTrackIds.push(sequencedTrackIdBelongingToDescendant)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasOneOrMoreStatics) {
|
||||||
|
contextMenuItems.push(
|
||||||
|
/**
|
||||||
|
* TODO This is surely confusing for the user if the descendants don't have overrides.
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
label: 'Reset all to default',
|
||||||
|
callback: () => {
|
||||||
|
getStudio()!.transaction(({unset}) => {
|
||||||
|
unset(pointerToProp)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Sequence all',
|
||||||
|
callback: () => {
|
||||||
|
getStudio()!.transaction(({stateEditors}) => {
|
||||||
|
for (const {path, conf} of iteratePropType(
|
||||||
|
propConfig,
|
||||||
|
pathToProp,
|
||||||
|
)) {
|
||||||
|
if (isPropConfigComposite(conf)) continue
|
||||||
|
const propAddress = {...obj.address, pathToProp: path}
|
||||||
|
|
||||||
|
stateEditors.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsSequenced(
|
||||||
|
propAddress,
|
||||||
|
propConfig,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasOneOrMoreSequencedTracks) {
|
||||||
|
contextMenuItems.push({
|
||||||
|
label: 'Make all static',
|
||||||
|
callback: () => {
|
||||||
|
getStudio()!.transaction(({stateEditors}) => {
|
||||||
|
for (const {path: subPath, conf} of iteratePropType(
|
||||||
|
propConfig,
|
||||||
|
[],
|
||||||
|
)) {
|
||||||
|
if (isPropConfigComposite(conf)) continue
|
||||||
|
const propAddress = {
|
||||||
|
...obj.address,
|
||||||
|
pathToProp: [...pathToProp, ...subPath],
|
||||||
|
}
|
||||||
|
const pointerToSub = pointerDeep(pointerToProp, subPath)
|
||||||
|
|
||||||
|
stateEditors.coreByProject.historic.sheetsById.sequence.setPrimitivePropAsStatic(
|
||||||
|
{
|
||||||
|
...propAddress,
|
||||||
|
value: obj.getValueByPointer(pointerToSub as $IntentionalAny),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasOneOrMoreSequencedTracks) {
|
||||||
|
const sequenceTrackId = possibleSequenceTrackIds
|
||||||
|
const nearbyKeyframeControls = prism.sub(
|
||||||
|
'lcr',
|
||||||
|
(): NearbyKeyframesControls => {
|
||||||
|
const sequencePosition = val(
|
||||||
|
obj.sheet.getSequence().positionDerivation,
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
2/10 perf concern:
|
||||||
|
When displaying a hierarchy like {props: {transform: {position: {x, y, z}}}},
|
||||||
|
we'd be recalculating this variable for both `position` and `transform`. While
|
||||||
|
we _could_ be re-using the calculation of `transform` in `position`, I think
|
||||||
|
it's unlikely that this optimization would matter.
|
||||||
|
*/
|
||||||
|
const nearbyKeyframesInEachTrack = listOfDescendantTrackIds
|
||||||
|
.map((trackId) => ({
|
||||||
|
trackId,
|
||||||
|
track: val(
|
||||||
|
obj.template.project.pointers.historic.sheetsById[
|
||||||
|
obj.address.sheetId
|
||||||
|
].sequence.tracksByObject[obj.address.objectKey].trackData[
|
||||||
|
trackId
|
||||||
|
],
|
||||||
|
),
|
||||||
|
}))
|
||||||
|
.filter(({track}) => !!track)
|
||||||
|
.map((s) => ({
|
||||||
|
...s,
|
||||||
|
nearbies: getNearbyKeyframesOfTrack(s.track, sequencePosition),
|
||||||
|
}))
|
||||||
|
|
||||||
|
const hasCur = nearbyKeyframesInEachTrack.find(
|
||||||
|
({nearbies}) => !!nearbies.cur,
|
||||||
|
)
|
||||||
|
const allCur = nearbyKeyframesInEachTrack.every(
|
||||||
|
({nearbies}) => !!nearbies.cur,
|
||||||
|
)
|
||||||
|
|
||||||
|
const closestPrev = nearbyKeyframesInEachTrack.reduce<
|
||||||
|
undefined | number
|
||||||
|
>((acc, s) => {
|
||||||
|
if (s.nearbies.prev) {
|
||||||
|
if (acc === undefined) {
|
||||||
|
return s.nearbies.prev.position
|
||||||
|
} else {
|
||||||
|
return Math.max(s.nearbies.prev.position, acc)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
}, undefined)
|
||||||
|
|
||||||
|
const closestNext = nearbyKeyframesInEachTrack.reduce<
|
||||||
|
undefined | number
|
||||||
|
>((acc, s) => {
|
||||||
|
if (s.nearbies.next) {
|
||||||
|
if (acc === undefined) {
|
||||||
|
return s.nearbies.next.position
|
||||||
|
} else {
|
||||||
|
return Math.min(s.nearbies.next.position, acc)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return acc
|
||||||
|
}
|
||||||
|
}, undefined)
|
||||||
|
|
||||||
|
return {
|
||||||
|
cur: {
|
||||||
|
type: hasCur ? 'on' : 'off',
|
||||||
|
toggle: () => {
|
||||||
|
if (allCur) {
|
||||||
|
getStudio().transaction((api) => {
|
||||||
|
api.unset(pointerToProp)
|
||||||
|
})
|
||||||
|
} else if (hasCur) {
|
||||||
|
getStudio().transaction((api) => {
|
||||||
|
api.set(pointerToProp, val(pointerToProp))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
getStudio().transaction((api) => {
|
||||||
|
api.set(pointerToProp, val(pointerToProp))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
prev:
|
||||||
|
closestPrev !== undefined
|
||||||
|
? {
|
||||||
|
position: closestPrev,
|
||||||
|
jump: () => {
|
||||||
|
obj.sheet.getSequence().position = closestPrev
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
next:
|
||||||
|
closestNext !== undefined
|
||||||
|
? {
|
||||||
|
position: closestNext,
|
||||||
|
jump: () => {
|
||||||
|
obj.sheet.getSequence().position = closestNext
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[sequenceTrackId],
|
||||||
|
)
|
||||||
|
|
||||||
|
const nextPrevKeyframeCursors = (
|
||||||
|
<NextPrevKeyframeCursors {...nearbyKeyframeControls} />
|
||||||
|
)
|
||||||
|
|
||||||
|
const ret: HasSequences = {
|
||||||
|
...common,
|
||||||
|
type: 'HasSequences',
|
||||||
|
controlIndicators: nextPrevKeyframeCursors,
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...common,
|
||||||
|
type: 'AllStatic',
|
||||||
|
controlIndicators: (
|
||||||
|
<DefaultOrStaticValueIndicator hasStaticOverride={false} />
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
}
|
|
@ -1,10 +1,7 @@
|
||||||
import get from 'lodash-es/get'
|
import get from 'lodash-es/get'
|
||||||
import last from 'lodash-es/last'
|
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
import type {Pointer} from '@theatre/dataverse'
|
import type {Pointer} from '@theatre/dataverse'
|
||||||
import {getPointerParts, prism, val} from '@theatre/dataverse'
|
import {getPointerParts, prism, val} from '@theatre/dataverse'
|
||||||
import type {Keyframe} from '@theatre/core/projects/store/types/SheetState_Historic'
|
|
||||||
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
import type SheetObject from '@theatre/core/sheetObjects/SheetObject'
|
||||||
import getStudio from '@theatre/studio/getStudio'
|
import getStudio from '@theatre/studio/getStudio'
|
||||||
import type Scrub from '@theatre/studio/Scrub'
|
import type Scrub from '@theatre/studio/Scrub'
|
||||||
|
@ -15,8 +12,10 @@ import type {SerializablePrimitive as SerializablePrimitive} from '@theatre/shar
|
||||||
import type {PropTypeConfig_AllSimples} from '@theatre/core/propTypes'
|
import type {PropTypeConfig_AllSimples} from '@theatre/core/propTypes'
|
||||||
import {isPropConfSequencable} from '@theatre/shared/propTypes/utils'
|
import {isPropConfSequencable} from '@theatre/shared/propTypes/utils'
|
||||||
import type {SequenceTrackId} from '@theatre/shared/utils/ids'
|
import type {SequenceTrackId} from '@theatre/shared/utils/ids'
|
||||||
|
|
||||||
import DefaultOrStaticValueIndicator from './DefaultValueIndicator'
|
import DefaultOrStaticValueIndicator from './DefaultValueIndicator'
|
||||||
|
import type {NearbyKeyframes} from './getNearbyKeyframesOfTrack'
|
||||||
|
import {getNearbyKeyframesOfTrack} from './getNearbyKeyframesOfTrack'
|
||||||
|
import type {NearbyKeyframesControls} from './NextPrevKeyframeCursors'
|
||||||
import NextPrevKeyframeCursors from './NextPrevKeyframeCursors'
|
import NextPrevKeyframeCursors from './NextPrevKeyframeCursors'
|
||||||
|
|
||||||
interface EditingToolsCommon<T> {
|
interface EditingToolsCommon<T> {
|
||||||
|
@ -170,33 +169,10 @@ export function useEditingToolsForSimplePropInDetailsPanel<
|
||||||
sequenceTrackId
|
sequenceTrackId
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
if (!track || track.keyframes.length === 0) return {}
|
const sequencePosition = val(
|
||||||
|
obj.sheet.getSequence().positionDerivation,
|
||||||
const pos = val(obj.sheet.getSequence().positionDerivation)
|
)
|
||||||
|
return getNearbyKeyframesOfTrack(track, sequencePosition)
|
||||||
const i = track.keyframes.findIndex((kf) => kf.position >= pos)
|
|
||||||
|
|
||||||
if (i === -1)
|
|
||||||
return {
|
|
||||||
prev: last(track.keyframes),
|
|
||||||
}
|
|
||||||
|
|
||||||
const k = track.keyframes[i]!
|
|
||||||
if (k.position === pos) {
|
|
||||||
return {
|
|
||||||
prev: i > 0 ? track.keyframes[i - 1] : undefined,
|
|
||||||
cur: k,
|
|
||||||
next:
|
|
||||||
i === track.keyframes.length - 1
|
|
||||||
? undefined
|
|
||||||
: track.keyframes[i + 1],
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
next: k,
|
|
||||||
prev: i > 0 ? track.keyframes[i - 1] : undefined,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
[sequenceTrackId],
|
[sequenceTrackId],
|
||||||
)
|
)
|
||||||
|
@ -215,13 +191,10 @@ export function useEditingToolsForSimplePropInDetailsPanel<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextPrevKeyframeCursors = (
|
const controls: NearbyKeyframesControls = {
|
||||||
<NextPrevKeyframeCursors
|
cur: {
|
||||||
{...nearbyKeyframes}
|
type: nearbyKeyframes.cur ? 'on' : 'off',
|
||||||
jumpToPosition={(position) => {
|
toggle: () => {
|
||||||
obj.sheet.getSequence().position = position
|
|
||||||
}}
|
|
||||||
toggleKeyframeOnCurrentPosition={() => {
|
|
||||||
if (nearbyKeyframes.cur) {
|
if (nearbyKeyframes.cur) {
|
||||||
getStudio()!.transaction((api) => {
|
getStudio()!.transaction((api) => {
|
||||||
api.unset(pointerToProp)
|
api.unset(pointerToProp)
|
||||||
|
@ -231,8 +204,32 @@ export function useEditingToolsForSimplePropInDetailsPanel<
|
||||||
api.set(pointerToProp, common.value)
|
api.set(pointerToProp, common.value)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}}
|
},
|
||||||
/>
|
},
|
||||||
|
prev:
|
||||||
|
nearbyKeyframes.prev !== undefined
|
||||||
|
? {
|
||||||
|
position: nearbyKeyframes.prev.position,
|
||||||
|
jump: () => {
|
||||||
|
obj.sheet.getSequence().position =
|
||||||
|
nearbyKeyframes.prev!.position
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
next:
|
||||||
|
nearbyKeyframes.next !== undefined
|
||||||
|
? {
|
||||||
|
position: nearbyKeyframes.next.position,
|
||||||
|
jump: () => {
|
||||||
|
obj.sheet.getSequence().position =
|
||||||
|
nearbyKeyframes.next!.position
|
||||||
|
},
|
||||||
|
}
|
||||||
|
: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextPrevKeyframeCursors = (
|
||||||
|
<NextPrevKeyframeCursors {...controls} />
|
||||||
)
|
)
|
||||||
|
|
||||||
const ret: EditingToolsSequenced<T> = {
|
const ret: EditingToolsSequenced<T> = {
|
||||||
|
@ -299,22 +296,6 @@ export function useEditingToolsForSimplePropInDetailsPanel<
|
||||||
}, [])
|
}, [])
|
||||||
}
|
}
|
||||||
|
|
||||||
type NearbyKeyframes = {
|
|
||||||
prev?: Keyframe
|
|
||||||
cur?: Keyframe
|
|
||||||
next?: Keyframe
|
|
||||||
}
|
|
||||||
|
|
||||||
export const shadeToColor: {[K in Shade]: string} = {
|
|
||||||
Default: '#222',
|
|
||||||
Static: '#333',
|
|
||||||
Static_BeingScrubbed: '#91a100',
|
|
||||||
Sequenced_OnKeyframe: '#700202',
|
|
||||||
Sequenced_OnKeyframe_BeingScrubbed: '#c50000',
|
|
||||||
Sequenced_BeingInterpolated: '#0387a8',
|
|
||||||
Sequened_NotBeingInterpolated: '#004c5f',
|
|
||||||
}
|
|
||||||
|
|
||||||
type Shade =
|
type Shade =
|
||||||
| 'Default'
|
| 'Default'
|
||||||
| 'Static'
|
| 'Static'
|
||||||
|
|
|
@ -34,7 +34,9 @@ type OnDragCallback = (
|
||||||
dyFromLastEvent: number,
|
dyFromLastEvent: number,
|
||||||
) => void
|
) => void
|
||||||
|
|
||||||
type OnDragEndCallback = (dragHappened: boolean) => void
|
type OnClickCallback = (mouseUpEvent: MouseEvent) => void
|
||||||
|
|
||||||
|
type OnDragEndCallback = (dragHappened: boolean, event?: MouseEvent) => void
|
||||||
|
|
||||||
export type UseDragOpts = {
|
export type UseDragOpts = {
|
||||||
/**
|
/**
|
||||||
|
@ -88,6 +90,7 @@ export type UseDragOpts = {
|
||||||
*/
|
*/
|
||||||
onDragEnd?: OnDragEndCallback
|
onDragEnd?: OnDragEndCallback
|
||||||
onDrag: OnDragCallback
|
onDrag: OnDragCallback
|
||||||
|
onClick?: OnClickCallback
|
||||||
}
|
}
|
||||||
|
|
||||||
// which mouse button to use the drag event
|
// which mouse button to use the drag event
|
||||||
|
@ -170,7 +173,8 @@ export default function useDrag(
|
||||||
const callbacksRef = useRef<{
|
const callbacksRef = useRef<{
|
||||||
onDrag: OnDragCallback
|
onDrag: OnDragCallback
|
||||||
onDragEnd: OnDragEndCallback
|
onDragEnd: OnDragEndCallback
|
||||||
}>({onDrag: noop, onDragEnd: noop})
|
onClick: OnClickCallback
|
||||||
|
}>({onDrag: noop, onDragEnd: noop, onClick: noop})
|
||||||
|
|
||||||
const capturedPointerRef = useRef<undefined | CapturedPointer>()
|
const capturedPointerRef = useRef<undefined | CapturedPointer>()
|
||||||
// needed to have a state on the react lifecycle which can be updated
|
// needed to have a state on the react lifecycle which can be updated
|
||||||
|
@ -239,13 +243,16 @@ export default function useDrag(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dragEndHandler = () => {
|
const dragEndHandler = (e: MouseEvent) => {
|
||||||
removeDragListeners()
|
removeDragListeners()
|
||||||
if (!stateRef.current.domDragStarted) return
|
if (!stateRef.current.domDragStarted) return
|
||||||
const dragHappened = stateRef.current.detection.detected
|
const dragHappened = stateRef.current.detection.detected
|
||||||
stateRef.current = {domDragStarted: false}
|
stateRef.current = {domDragStarted: false}
|
||||||
if (opts.shouldPointerLock && !isSafari) document.exitPointerLock()
|
if (opts.shouldPointerLock && !isSafari) document.exitPointerLock()
|
||||||
callbacksRef.current.onDragEnd(dragHappened)
|
callbacksRef.current.onDragEnd(dragHappened)
|
||||||
|
if (!dragHappened) {
|
||||||
|
callbacksRef.current.onClick(e)
|
||||||
|
}
|
||||||
ensureIsDraggingUpToDateForReactLifecycle()
|
ensureIsDraggingUpToDateForReactLifecycle()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,6 +303,7 @@ export default function useDrag(
|
||||||
|
|
||||||
callbacksRef.current.onDrag = returnOfOnDragStart.onDrag
|
callbacksRef.current.onDrag = returnOfOnDragStart.onDrag
|
||||||
callbacksRef.current.onDragEnd = returnOfOnDragStart.onDragEnd ?? noop
|
callbacksRef.current.onDragEnd = returnOfOnDragStart.onDragEnd ?? noop
|
||||||
|
callbacksRef.current.onClick = returnOfOnDragStart.onClick ?? noop
|
||||||
|
|
||||||
// need to capture pointer after we know the provided handler wants to handle drag start
|
// need to capture pointer after we know the provided handler wants to handle drag start
|
||||||
capturedPointerRef.current = capturePointer('Drag start')
|
capturedPointerRef.current = capturePointer('Drag start')
|
||||||
|
|
Loading…
Reference in a new issue