From 2bf081478fe66402207a17ffc9343d3a1742b4f9 Mon Sep 17 00:00:00 2001 From: Elliot Date: Fri, 25 Mar 2022 13:06:20 -0400 Subject: [PATCH] Fix keyframe pasting to respect interpolation graph (#107) --- theatre/core/src/propTypes/index.ts | 2 +- theatre/core/src/sheetObjects/SheetObject.ts | 16 +++----------- theatre/shared/src/propTypes/utils.ts | 22 +++++++++++++++++++ .../BasicKeyframedTrack.tsx | 1 + .../KeyframeEditor/Dot.tsx | 14 +++++++----- .../BasicKeyframedTrack.tsx | 13 +++++++---- .../KeyframeEditor/Curve.tsx | 9 ++++++-- .../KeyframeEditor/KeyframeEditor.tsx | 2 ++ .../GraphEditor/GraphEditor.tsx | 6 ++--- theatre/studio/src/store/stateEditors.ts | 5 +++-- 10 files changed, 59 insertions(+), 31 deletions(-) diff --git a/theatre/core/src/propTypes/index.ts b/theatre/core/src/propTypes/index.ts index aa2701c..9b0e167 100644 --- a/theatre/core/src/propTypes/index.ts +++ b/theatre/core/src/propTypes/index.ts @@ -506,7 +506,7 @@ export function stringLiteral( export type Sanitizer = (value: unknown) => T | undefined export type Interpolator = (left: T, right: T, progression: number) => T -interface IBasePropType { +export interface IBasePropType { valueType: ValueType [propTypeSymbol]: 'TheatrePropType' label: string | undefined diff --git a/theatre/core/src/sheetObjects/SheetObject.ts b/theatre/core/src/sheetObjects/SheetObject.ts index c5544f3..23fa671 100644 --- a/theatre/core/src/sheetObjects/SheetObject.ts +++ b/theatre/core/src/sheetObjects/SheetObject.ts @@ -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, diff --git a/theatre/shared/src/propTypes/utils.ts b/theatre/shared/src/propTypes/utils.ts index 8423ce2..72a5545 100644 --- a/theatre/shared/src/propTypes/utils.ts +++ b/theatre/shared/src/propTypes/utils.ts @@ -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( + value: unknown, + propConfig: IBasePropType, +): PropValueType | unknown { + const sanitize = propConfig.sanitize + if (!sanitize) return value + + const sanitizedVal = sanitize(value) + if (typeof sanitizedVal === 'undefined') { + return propConfig.default + } else { + return sanitizedVal + } +} diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/BasicKeyframedTrack.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/BasicKeyframedTrack.tsx index 6b2133f..c35f0a0 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/BasicKeyframedTrack.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/BasicKeyframedTrack.tsx @@ -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, }, diff --git a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/Dot.tsx b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/Dot.tsx index 141f5a6..ae25d22 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/Dot.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/DopeSheet/Right/BasicKeyframedTrack/KeyframeEditor/Dot.tsx @@ -107,17 +107,19 @@ const Dot: React.FC = (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) => diff --git a/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/BasicKeyframedTrack.tsx b/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/BasicKeyframedTrack.tsx index 1b70672..8304f2d 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/BasicKeyframedTrack.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/BasicKeyframedTrack.tsx @@ -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) => ( { - 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] diff --git a/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/Curve.tsx b/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/Curve.tsx index 24b28c4..2ab29e2 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/Curve.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/Curve.tsx @@ -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 = (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) diff --git a/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/KeyframeEditor.tsx b/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/KeyframeEditor.tsx index 3425b6f..2bc5379 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/KeyframeEditor.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/BasicKeyframedTrack/KeyframeEditor/KeyframeEditor.tsx @@ -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] diff --git a/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/GraphEditor.tsx b/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/GraphEditor.tsx index 5dc659c..bf56382 100644 --- a/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/GraphEditor.tsx +++ b/theatre/studio/src/panels/SequenceEditorPanel/GraphEditor/GraphEditor.tsx @@ -113,9 +113,9 @@ const GraphEditor: React.FC<{ > {graphs} diff --git a/theatre/studio/src/store/stateEditors.ts b/theatre/studio/src/store/stateEditors.ts index 1f521a6..ce555ed 100644 --- a/theatre/studio/src/store/stateEditors.ts +++ b/theatre/studio/src/store/stateEditors.ts @@ -542,6 +542,7 @@ namespace stateEditors { p: WithoutSheetInstance & { 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, }) }