Fix keyframe pasting to respect interpolation graph (#107)

This commit is contained in:
Elliot 2022-03-25 13:06:20 -04:00 committed by GitHub
parent ffdebebfff
commit 2bf081478f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 59 additions and 31 deletions

View file

@ -506,7 +506,7 @@ export function stringLiteral<Opts extends {[key in string]: string}>(
export type Sanitizer<T> = (value: unknown) => T | undefined
export type Interpolator<T> = (left: T, right: T, progression: number) => T
interface IBasePropType<ValueType, PropTypes = ValueType> {
export interface IBasePropType<ValueType, PropTypes = ValueType> {
valueType: ValueType
[propTypeSymbol]: 'TheatrePropType'
label: string | undefined

View file

@ -23,7 +23,7 @@ import {Atom, getPointerParts, pointer, prism, val} from '@theatre/dataverse'
import type SheetObjectTemplate from './SheetObjectTemplate'
import TheatreSheetObject from './TheatreSheetObject'
import type {Interpolator} from '@theatre/core/propTypes'
import {getPropConfigByPath} from '@theatre/shared/propTypes/utils'
import {getPropConfigByPath, valueInProp} from '@theatre/shared/propTypes/utils'
// type Everything = {
// final: SerializableMap
@ -159,7 +159,6 @@ export default class SheetObject implements IdentityDerivationProvider {
pathToProp,
)!
const sanitize = propConfig.sanitize!
const interpolate =
propConfig.interpolate! as Interpolator<$IntentionalAny>
@ -169,21 +168,12 @@ export default class SheetObject implements IdentityDerivationProvider {
if (!triple)
return valsAtom.setIn(pathToProp, propConfig!.default)
const leftSanitized = sanitize(triple.left)
const left =
typeof leftSanitized === 'undefined'
? propConfig.default
: leftSanitized
const left = valueInProp(triple.left, propConfig)
if (triple.right === undefined)
return valsAtom.setIn(pathToProp, left)
const rightSanitized = sanitize(triple.right)
const right =
typeof rightSanitized === 'undefined'
? propConfig.default
: rightSanitized
const right = valueInProp(triple.right, propConfig)
return valsAtom.setIn(
pathToProp,

View file

@ -1,4 +1,5 @@
import type {
IBasePropType,
PropTypeConfig,
PropTypeConfig_Compound,
PropTypeConfig_Enum,
@ -28,3 +29,24 @@ export function getPropConfigByPath(
return getPropConfigByPath(sub, rest)
}
/**
* @param value - An arbitrary value. May be matching the prop's type or not
* @param propConfig - The configuration object for a prop
* @returns value if it matches the prop's type (or if the prop doesn't have a sanitizer),
* otherwise returns the default value for the prop
*/
export function valueInProp<PropValueType>(
value: unknown,
propConfig: IBasePropType<PropValueType>,
): PropValueType | unknown {
const sanitize = propConfig.sanitize
if (!sanitize) return value
const sanitizedVal = sanitize(value)
if (typeof sanitizedVal === 'undefined') {
return propConfig.default
} else {
return sanitizedVal
}
}

View file

@ -118,6 +118,7 @@ function pasteKeyframesContextMenuItem(
...props.leaf.sheetObject.address,
trackId: props.leaf.trackId,
position: sequence.position + keyframe.position - keyframeOffset,
handles: keyframe.handles,
value: keyframe.value,
snappingFunction: sequence.closestGridPosition,
},

View file

@ -107,17 +107,19 @@ const Dot: React.FC<IProps> = (props) => {
export default Dot
function useKeyframeContextMenu(node: HTMLDivElement | null, props: IProps) {
const maybeKeyframeIds = selectedKeyframeIdsIfInSingleTrack(props.selection)
const maybeSelectedKeyframeIds = selectedKeyframeIdsIfInSingleTrack(
props.selection,
)
const keyframeSelectionItems = maybeKeyframeIds
? [copyKeyFrameContextMenuItem(props, maybeKeyframeIds)]
: []
const keyframeSelectionItem = maybeSelectedKeyframeIds
? copyKeyFrameContextMenuItem(props, maybeSelectedKeyframeIds)
: copyKeyFrameContextMenuItem(props, [props.keyframe.id])
const deleteItem = deleteSelectionOrKeyframeContextMenuItem(props)
return useContextMenu(node, {
items: () => {
return [...keyframeSelectionItems, deleteItem]
return [keyframeSelectionItem, deleteItem]
},
})
}
@ -263,7 +265,7 @@ function deleteSelectionOrKeyframeContextMenuItem(props: IProps) {
function copyKeyFrameContextMenuItem(props: IProps, keyframeIds: string[]) {
return {
label: 'Copy Keyframes',
label: keyframeIds.length > 1 ? 'Copy selection' : 'Copy keyframe',
callback: () => {
const keyframes = keyframeIds.map(
(keyframeId) =>

View file

@ -11,7 +11,8 @@ import React, {useMemo, useRef, useState} from 'react'
import type {SequenceEditorPanelLayout} from '@theatre/studio/panels/SequenceEditorPanel/layout/layout'
import {graphEditorColors} from '@theatre/studio/panels/SequenceEditorPanel/GraphEditor/GraphEditor'
import KeyframeEditor from './KeyframeEditor/KeyframeEditor'
import {getPropConfigByPath} from '@theatre/shared/propTypes/utils'
import {getPropConfigByPath, valueInProp} from '@theatre/shared/propTypes/utils'
import type {PropTypeConfig} from '@theatre/core/propTypes'
export type ExtremumSpace = {
fromValueSpace: (v: number) => number
@ -55,7 +56,7 @@ const BasicKeyframedTrack: React.FC<{
const extremumSpace: ExtremumSpace = useMemo(() => {
const extremums =
propConfig.isScalar === true
? calculateScalarExtremums(trackData.keyframes)
? calculateScalarExtremums(trackData.keyframes, propConfig)
: calculateNonScalarExtremums(trackData.keyframes)
const fromValueSpace = (val: number): number =>
@ -84,6 +85,7 @@ const BasicKeyframedTrack: React.FC<{
const keyframeEditors = trackData.keyframes.map((kf, index) => (
<KeyframeEditor
propConfig={propConfig}
keyframe={kf}
index={index}
trackData={trackData}
@ -114,7 +116,10 @@ export default BasicKeyframedTrack
type Extremums = [min: number, max: number]
function calculateScalarExtremums(keyframes: Keyframe[]): Extremums {
function calculateScalarExtremums(
keyframes: Keyframe[],
propConfig: PropTypeConfig,
): Extremums {
let min = Infinity,
max = -Infinity
@ -124,7 +129,7 @@ function calculateScalarExtremums(keyframes: Keyframe[]): Extremums {
}
keyframes.forEach((cur, i) => {
const curVal = typeof cur.value === 'number' ? cur.value : 0
const curVal = valueInProp(cur.value, propConfig) as number
check(curVal)
if (!cur.connectedRight) return
const next = keyframes[i + 1]

View file

@ -1,3 +1,4 @@
import {valueInProp} from '@theatre/shared/propTypes/utils'
import getStudio from '@theatre/studio/getStudio'
import useContextMenu from '@theatre/studio/uiComponents/simpleContextMenu/useContextMenu'
import useRefAndState from '@theatre/studio/utils/useRefAndState'
@ -25,8 +26,12 @@ const Curve: React.FC<IProps> = (props) => {
const [contextMenu] = useConnectorContextMenu(node, props)
const curValue = props.isScalar ? (cur.value as number) : 0
const nextValue = props.isScalar ? (next.value as number) : 1
const curValue = props.isScalar
? (valueInProp(cur.value, props.propConfig) as number)
: 0
const nextValue = props.isScalar
? (valueInProp(next.value, props.propConfig) as number)
: 1
const leftYInExtremumSpace = props.extremumSpace.fromValueSpace(curValue)
const rightYInExtremumSpace = props.extremumSpace.fromValueSpace(nextValue)

View file

@ -15,6 +15,7 @@ import CurveHandle from './CurveHandle'
import GraphEditorDotScalar from './GraphEditorDotScalar'
import GraphEditorDotNonScalar from './GraphEditorDotNonScalar'
import GraphEditorNonScalarDash from './GraphEditorNonScalarDash'
import type {PropTypeConfig} from '@theatre/core/propTypes'
const Container = styled.g`
/* position: absolute; */
@ -32,6 +33,7 @@ const KeyframeEditor: React.FC<{
extremumSpace: ExtremumSpace
isScalar: boolean
color: keyof typeof graphEditorColors
propConfig: PropTypeConfig
}> = (props) => {
const {index, trackData, isScalar} = props
const cur = trackData.keyframes[index]

View file

@ -113,9 +113,9 @@ const GraphEditor: React.FC<{
>
<g
style={{
transform: `translate(0, ${val(
layoutP.graphEditorDims.padding.top,
)}px)`,
transform: `translate(${val(
layoutP.scaledSpace.leftPadding,
)}px, ${val(layoutP.graphEditorDims.padding.top)}px)`,
}}
>
{graphs}

View file

@ -542,6 +542,7 @@ namespace stateEditors {
p: WithoutSheetInstance<SheetObjectAddress> & {
trackId: string
position: number
handles?: [number, number, number, number]
value: T
snappingFunction: SnappingFunction
},
@ -567,7 +568,7 @@ namespace stateEditors {
id: generateKeyframeId(),
position,
connectedRight: true,
handles: [0.5, 1, 0.5, 0],
handles: p.handles || [0.5, 1, 0.5, 0],
value: p.value,
})
return
@ -577,7 +578,7 @@ namespace stateEditors {
id: generateKeyframeId(),
position,
connectedRight: leftKeyframe.connectedRight,
handles: [0.5, 1, 0.5, 0],
handles: p.handles || [0.5, 1, 0.5, 0],
value: p.value,
})
}